Implement use of pause / resume protocol commands
[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 "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
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 void ToggleSecond P((void));
227
228 #ifdef WIN32
229        extern void ConsoleCreate();
230 #endif
231
232 ChessProgramState *WhitePlayer();
233 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
234 int VerifyDisplayMode P(());
235
236 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
237 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
238 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
239 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
240 void ics_update_width P((int new_width));
241 extern char installDir[MSG_SIZ];
242 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 Boolean abortMatch;
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 char lastMsg[MSG_SIZ];
271 ChessSquare pieceSweep = EmptySquare;
272 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
273 int promoDefaultAltered;
274 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
275
276 /* States for ics_getting_history */
277 #define H_FALSE 0
278 #define H_REQUESTED 1
279 #define H_GOT_REQ_HEADER 2
280 #define H_GOT_UNREQ_HEADER 3
281 #define H_GETTING_MOVES 4
282 #define H_GOT_UNWANTED_HEADER 5
283
284 /* whosays values for GameEnds */
285 #define GE_ICS 0
286 #define GE_ENGINE 1
287 #define GE_PLAYER 2
288 #define GE_FILE 3
289 #define GE_XBOARD 4
290 #define GE_ENGINE1 5
291 #define GE_ENGINE2 6
292
293 /* Maximum number of games in a cmail message */
294 #define CMAIL_MAX_GAMES 20
295
296 /* Different types of move when calling RegisterMove */
297 #define CMAIL_MOVE   0
298 #define CMAIL_RESIGN 1
299 #define CMAIL_DRAW   2
300 #define CMAIL_ACCEPT 3
301
302 /* Different types of result to remember for each game */
303 #define CMAIL_NOT_RESULT 0
304 #define CMAIL_OLD_RESULT 1
305 #define CMAIL_NEW_RESULT 2
306
307 /* Telnet protocol constants */
308 #define TN_WILL 0373
309 #define TN_WONT 0374
310 #define TN_DO   0375
311 #define TN_DONT 0376
312 #define TN_IAC  0377
313 #define TN_ECHO 0001
314 #define TN_SGA  0003
315 #define TN_PORT 23
316
317 char*
318 safeStrCpy (char *dst, const char *src, size_t count)
319 { // [HGM] made safe
320   int i;
321   assert( dst != NULL );
322   assert( src != NULL );
323   assert( count > 0 );
324
325   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
326   if(  i == count && dst[count-1] != NULLCHAR)
327     {
328       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
329       if(appData.debugMode)
330       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
331     }
332
333   return dst;
334 }
335
336 /* Some compiler can't cast u64 to double
337  * This function do the job for us:
338
339  * We use the highest bit for cast, this only
340  * works if the highest bit is not
341  * in use (This should not happen)
342  *
343  * We used this for all compiler
344  */
345 double
346 u64ToDouble (u64 value)
347 {
348   double r;
349   u64 tmp = value & u64Const(0x7fffffffffffffff);
350   r = (double)(s64)tmp;
351   if (value & u64Const(0x8000000000000000))
352        r +=  9.2233720368547758080e18; /* 2^63 */
353  return r;
354 }
355
356 /* Fake up flags for now, as we aren't keeping track of castling
357    availability yet. [HGM] Change of logic: the flag now only
358    indicates the type of castlings allowed by the rule of the game.
359    The actual rights themselves are maintained in the array
360    castlingRights, as part of the game history, and are not probed
361    by this function.
362  */
363 int
364 PosFlags (index)
365 {
366   int flags = F_ALL_CASTLE_OK;
367   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
368   switch (gameInfo.variant) {
369   case VariantSuicide:
370     flags &= ~F_ALL_CASTLE_OK;
371   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
372     flags |= F_IGNORE_CHECK;
373   case VariantLosers:
374     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375     break;
376   case VariantAtomic:
377     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378     break;
379   case VariantKriegspiel:
380     flags |= F_KRIEGSPIEL_CAPTURE;
381     break;
382   case VariantCapaRandom:
383   case VariantFischeRandom:
384     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
385   case VariantNoCastle:
386   case VariantShatranj:
387   case VariantCourier:
388   case VariantMakruk:
389   case VariantGrand:
390     flags &= ~F_ALL_CASTLE_OK;
391     break;
392   default:
393     break;
394   }
395   return flags;
396 }
397
398 FILE *gameFileFP, *debugFP, *serverFP;
399 char *currentDebugFile; // [HGM] debug split: to remember name
400
401 /*
402     [AS] Note: sometimes, the sscanf() function is used to parse the input
403     into a fixed-size buffer. Because of this, we must be prepared to
404     receive strings as long as the size of the input buffer, which is currently
405     set to 4K for Windows and 8K for the rest.
406     So, we must either allocate sufficiently large buffers here, or
407     reduce the size of the input buffer in the input reading part.
408 */
409
410 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
411 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
412 char thinkOutput1[MSG_SIZ*10];
413
414 ChessProgramState first, second, pairing;
415
416 /* premove variables */
417 int premoveToX = 0;
418 int premoveToY = 0;
419 int premoveFromX = 0;
420 int premoveFromY = 0;
421 int premovePromoChar = 0;
422 int gotPremove = 0;
423 Boolean alarmSounded;
424 /* end premove variables */
425
426 char *ics_prefix = "$";
427 enum ICS_TYPE ics_type = ICS_GENERIC;
428
429 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
430 int pauseExamForwardMostMove = 0;
431 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
432 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
433 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
434 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
435 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
436 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
437 int whiteFlag = FALSE, blackFlag = FALSE;
438 int userOfferedDraw = FALSE;
439 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
440 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
441 int cmailMoveType[CMAIL_MAX_GAMES];
442 long ics_clock_paused = 0;
443 ProcRef icsPR = NoProc, cmailPR = NoProc;
444 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
445 GameMode gameMode = BeginningOfGame;
446 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
447 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
448 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
449 int hiddenThinkOutputState = 0; /* [AS] */
450 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
451 int adjudicateLossPlies = 6;
452 char white_holding[64], black_holding[64];
453 TimeMark lastNodeCountTime;
454 long lastNodeCount=0;
455 int shiftKey, controlKey; // [HGM] set by mouse handler
456
457 int have_sent_ICS_logon = 0;
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
461 Boolean adjustedClock;
462 long timeControl_2; /* [AS] Allow separate time controls */
463 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
464 long timeRemaining[2][MAX_MOVES];
465 int matchGame = 0, nextGame = 0, roundNr = 0;
466 Boolean waitingForGame = FALSE;
467 TimeMark programStartTime, pauseStart;
468 char ics_handle[MSG_SIZ];
469 int have_set_title = 0;
470
471 /* animateTraining preserves the state of appData.animate
472  * when Training mode is activated. This allows the
473  * response to be animated when appData.animate == TRUE and
474  * appData.animateDragging == TRUE.
475  */
476 Boolean animateTraining;
477
478 GameInfo gameInfo;
479
480 AppData appData;
481
482 Board boards[MAX_MOVES];
483 /* [HGM] Following 7 needed for accurate legality tests: */
484 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
485 signed char  initialRights[BOARD_FILES];
486 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
487 int   initialRulePlies, FENrulePlies;
488 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
489 int loadFlag = 0;
490 Boolean shuffleOpenings;
491 int mute; // mute all sounds
492
493 // [HGM] vari: next 12 to save and restore variations
494 #define MAX_VARIATIONS 10
495 int framePtr = MAX_MOVES-1; // points to free stack entry
496 int storedGames = 0;
497 int savedFirst[MAX_VARIATIONS];
498 int savedLast[MAX_VARIATIONS];
499 int savedFramePtr[MAX_VARIATIONS];
500 char *savedDetails[MAX_VARIATIONS];
501 ChessMove savedResult[MAX_VARIATIONS];
502
503 void PushTail P((int firstMove, int lastMove));
504 Boolean PopTail P((Boolean annotate));
505 void PushInner P((int firstMove, int lastMove));
506 void PopInner P((Boolean annotate));
507 void CleanupTail P((void));
508
509 ChessSquare  FIDEArray[2][BOARD_FILES] = {
510     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
511         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
513         BlackKing, BlackBishop, BlackKnight, BlackRook }
514 };
515
516 ChessSquare twoKingsArray[2][BOARD_FILES] = {
517     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520         BlackKing, BlackKing, BlackKnight, BlackRook }
521 };
522
523 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
524     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
525         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
526     { BlackRook, BlackMan, BlackBishop, BlackQueen,
527         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 };
529
530 ChessSquare SpartanArray[2][BOARD_FILES] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
532         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
534         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 };
536
537 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
541         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 };
543
544 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
545     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
546         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
548         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 };
550
551 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
553         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackMan, BlackFerz,
555         BlackKing, BlackMan, BlackKnight, BlackRook }
556 };
557
558
559 #if (BOARD_FILES>=10)
560 ChessSquare ShogiArray[2][BOARD_FILES] = {
561     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
562         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
563     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
564         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 };
566
567 ChessSquare XiangqiArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
569         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
571         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 };
573
574 ChessSquare CapablancaArray[2][BOARD_FILES] = {
575     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
576         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
578         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 };
580
581 ChessSquare GreatArray[2][BOARD_FILES] = {
582     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
583         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
584     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
585         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 };
587
588 ChessSquare JanusArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
590         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
591     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
592         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 };
594
595 ChessSquare GrandArray[2][BOARD_FILES] = {
596     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
597         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
598     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
599         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
600 };
601
602 #ifdef GOTHIC
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !GOTHIC
610 #define GothicArray CapablancaArray
611 #endif // !GOTHIC
612
613 #ifdef FALCON
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
616         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
618         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !FALCON
621 #define FalconArray CapablancaArray
622 #endif // !FALCON
623
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
630
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 };
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
641
642
643 Board initialPosition;
644
645
646 /* Convert str to a rating. Checks for special cases of "----",
647
648    "++++", etc. Also strips ()'s */
649 int
650 string_to_rating (char *str)
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats ()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit ()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine (ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions (ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("first"),
743   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
744      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
745 N_("second")
746 };
747
748 void
749 InitEngine (ChessProgramState *cps, int n)
750 {   // [HGM] all engine initialiation put in a function that does one engine
751
752     ClearOptions(cps);
753
754     cps->which = engineNames[n];
755     cps->maybeThinking = FALSE;
756     cps->pr = NoProc;
757     cps->isr = NULL;
758     cps->sendTime = 2;
759     cps->sendDrawOffers = 1;
760
761     cps->program = appData.chessProgram[n];
762     cps->host = appData.host[n];
763     cps->dir = appData.directory[n];
764     cps->initString = appData.engInitString[n];
765     cps->computerString = appData.computerString[n];
766     cps->useSigint  = TRUE;
767     cps->useSigterm = TRUE;
768     cps->reuse = appData.reuse[n];
769     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
770     cps->useSetboard = FALSE;
771     cps->useSAN = FALSE;
772     cps->usePing = FALSE;
773     cps->lastPing = 0;
774     cps->lastPong = 0;
775     cps->usePlayother = FALSE;
776     cps->useColors = TRUE;
777     cps->useUsermove = FALSE;
778     cps->sendICS = FALSE;
779     cps->sendName = appData.icsActive;
780     cps->sdKludge = FALSE;
781     cps->stKludge = FALSE;
782     TidyProgramName(cps->program, cps->host, cps->tidy);
783     cps->matchWins = 0;
784     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
785     cps->analysisSupport = 2; /* detect */
786     cps->analyzing = FALSE;
787     cps->initDone = FALSE;
788
789     /* New features added by Tord: */
790     cps->useFEN960 = FALSE;
791     cps->useOOCastle = TRUE;
792     /* End of new features added by Tord. */
793     cps->fenOverride  = appData.fenOverride[n];
794
795     /* [HGM] time odds: set factor for each machine */
796     cps->timeOdds  = appData.timeOdds[n];
797
798     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
799     cps->accumulateTC = appData.accumulateTC[n];
800     cps->maxNrOfSessions = 1;
801
802     /* [HGM] debug */
803     cps->debug = FALSE;
804
805     cps->supportsNPS = UNKNOWN;
806     cps->memSize = FALSE;
807     cps->maxCores = FALSE;
808     cps->egtFormats[0] = NULLCHAR;
809
810     /* [HGM] options */
811     cps->optionSettings  = appData.engOptions[n];
812
813     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
814     cps->isUCI = appData.isUCI[n]; /* [AS] */
815     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
816
817     if (appData.protocolVersion[n] > PROTOVER
818         || appData.protocolVersion[n] < 1)
819       {
820         char buf[MSG_SIZ];
821         int len;
822
823         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
824                        appData.protocolVersion[n]);
825         if( (len >= MSG_SIZ) && appData.debugMode )
826           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
827
828         DisplayFatalError(buf, 0, 2);
829       }
830     else
831       {
832         cps->protocolVersion = appData.protocolVersion[n];
833       }
834
835     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
836     ParseFeatures(appData.featureDefaults, cps);
837 }
838
839 ChessProgramState *savCps;
840
841 void
842 LoadEngine ()
843 {
844     int i;
845     if(WaitForEngine(savCps, LoadEngine)) return;
846     CommonEngineInit(); // recalculate time odds
847     if(gameInfo.variant != StringToVariant(appData.variant)) {
848         // we changed variant when loading the engine; this forces us to reset
849         Reset(TRUE, savCps != &first);
850         EditGameEvent(); // for consistency with other path, as Reset changes mode
851     }
852     InitChessProgram(savCps, FALSE);
853     SendToProgram("force\n", savCps);
854     DisplayMessage("", "");
855     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
856     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
857     ThawUI();
858     SetGNUMode();
859 }
860
861 void
862 ReplaceEngine (ChessProgramState *cps, int n)
863 {
864     EditGameEvent();
865     UnloadEngine(cps);
866     appData.noChessProgram = FALSE;
867     appData.clockMode = TRUE;
868     InitEngine(cps, n);
869     UpdateLogos(TRUE);
870     if(n) return; // only startup first engine immediately; second can wait
871     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872     LoadEngine();
873 }
874
875 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
876 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
877
878 static char resetOptions[] = 
879         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
880         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
881         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
883
884 void
885 FloatToFront(char **list, char *engineLine)
886 {
887     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
888     int i=0;
889     if(appData.recentEngines <= 0) return;
890     TidyProgramName(engineLine, "localhost", tidy+1);
891     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
892     strncpy(buf+1, *list, MSG_SIZ-50);
893     if(p = strstr(buf, tidy)) { // tidy name appears in list
894         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
895         while(*p++ = *++q); // squeeze out
896     }
897     strcat(tidy, buf+1); // put list behind tidy name
898     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
899     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
900     ASSIGN(*list, tidy+1);
901 }
902
903 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
904
905 void
906 Load (ChessProgramState *cps, int i)
907 {
908     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
909     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
910         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
911         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
912         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
913         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
914         appData.firstProtocolVersion = PROTOVER;
915         ParseArgsFromString(buf);
916         SwapEngines(i);
917         ReplaceEngine(cps, i);
918         FloatToFront(&appData.recentEngineList, engineLine);
919         return;
920     }
921     p = engineName;
922     while(q = strchr(p, SLASH)) p = q+1;
923     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
924     if(engineDir[0] != NULLCHAR) {
925         ASSIGN(appData.directory[i], engineDir); p = engineName;
926     } else if(p != engineName) { // derive directory from engine path, when not given
927         p[-1] = 0;
928         ASSIGN(appData.directory[i], engineName);
929         p[-1] = SLASH;
930         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
931     } else { ASSIGN(appData.directory[i], "."); }
932     if(params[0]) {
933         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
934         snprintf(command, MSG_SIZ, "%s %s", p, params);
935         p = command;
936     }
937     ASSIGN(appData.chessProgram[i], p);
938     appData.isUCI[i] = isUCI;
939     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
940     appData.hasOwnBookUCI[i] = hasBook;
941     if(!nickName[0]) useNick = FALSE;
942     if(useNick) ASSIGN(appData.pgnName[i], nickName);
943     if(addToList) {
944         int len;
945         char quote;
946         q = firstChessProgramNames;
947         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
948         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
949         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
950                         quote, p, quote, appData.directory[i], 
951                         useNick ? " -fn \"" : "",
952                         useNick ? nickName : "",
953                         useNick ? "\"" : "",
954                         v1 ? " -firstProtocolVersion 1" : "",
955                         hasBook ? "" : " -fNoOwnBookUCI",
956                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
957                         storeVariant ? " -variant " : "",
958                         storeVariant ? VariantName(gameInfo.variant) : "");
959         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
960         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
961         if(insert != q) insert[-1] = NULLCHAR;
962         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
963         if(q)   free(q);
964         FloatToFront(&appData.recentEngineList, buf);
965     }
966     ReplaceEngine(cps, i);
967 }
968
969 void
970 InitTimeControls ()
971 {
972     int matched, min, sec;
973     /*
974      * Parse timeControl resource
975      */
976     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
977                           appData.movesPerSession)) {
978         char buf[MSG_SIZ];
979         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
980         DisplayFatalError(buf, 0, 2);
981     }
982
983     /*
984      * Parse searchTime resource
985      */
986     if (*appData.searchTime != NULLCHAR) {
987         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
988         if (matched == 1) {
989             searchTime = min * 60;
990         } else if (matched == 2) {
991             searchTime = min * 60 + sec;
992         } else {
993             char buf[MSG_SIZ];
994             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
995             DisplayFatalError(buf, 0, 2);
996         }
997     }
998 }
999
1000 void
1001 InitBackEnd1 ()
1002 {
1003
1004     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1005     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1006
1007     GetTimeMark(&programStartTime);
1008     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1009     appData.seedBase = random() + (random()<<15);
1010     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1011
1012     ClearProgramStats();
1013     programStats.ok_to_send = 1;
1014     programStats.seen_stat = 0;
1015
1016     /*
1017      * Initialize game list
1018      */
1019     ListNew(&gameList);
1020
1021
1022     /*
1023      * Internet chess server status
1024      */
1025     if (appData.icsActive) {
1026         appData.matchMode = FALSE;
1027         appData.matchGames = 0;
1028 #if ZIPPY
1029         appData.noChessProgram = !appData.zippyPlay;
1030 #else
1031         appData.zippyPlay = FALSE;
1032         appData.zippyTalk = FALSE;
1033         appData.noChessProgram = TRUE;
1034 #endif
1035         if (*appData.icsHelper != NULLCHAR) {
1036             appData.useTelnet = TRUE;
1037             appData.telnetProgram = appData.icsHelper;
1038         }
1039     } else {
1040         appData.zippyTalk = appData.zippyPlay = FALSE;
1041     }
1042
1043     /* [AS] Initialize pv info list [HGM] and game state */
1044     {
1045         int i, j;
1046
1047         for( i=0; i<=framePtr; i++ ) {
1048             pvInfoList[i].depth = -1;
1049             boards[i][EP_STATUS] = EP_NONE;
1050             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1051         }
1052     }
1053
1054     InitTimeControls();
1055
1056     /* [AS] Adjudication threshold */
1057     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1058
1059     InitEngine(&first, 0);
1060     InitEngine(&second, 1);
1061     CommonEngineInit();
1062
1063     pairing.which = "pairing"; // pairing engine
1064     pairing.pr = NoProc;
1065     pairing.isr = NULL;
1066     pairing.program = appData.pairingEngine;
1067     pairing.host = "localhost";
1068     pairing.dir = ".";
1069
1070     if (appData.icsActive) {
1071         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1072     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1073         appData.clockMode = FALSE;
1074         first.sendTime = second.sendTime = 0;
1075     }
1076
1077 #if ZIPPY
1078     /* Override some settings from environment variables, for backward
1079        compatibility.  Unfortunately it's not feasible to have the env
1080        vars just set defaults, at least in xboard.  Ugh.
1081     */
1082     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1083       ZippyInit();
1084     }
1085 #endif
1086
1087     if (!appData.icsActive) {
1088       char buf[MSG_SIZ];
1089       int len;
1090
1091       /* Check for variants that are supported only in ICS mode,
1092          or not at all.  Some that are accepted here nevertheless
1093          have bugs; see comments below.
1094       */
1095       VariantClass variant = StringToVariant(appData.variant);
1096       switch (variant) {
1097       case VariantBughouse:     /* need four players and two boards */
1098       case VariantKriegspiel:   /* need to hide pieces and move details */
1099         /* case VariantFischeRandom: (Fabien: moved below) */
1100         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1101         if( (len >= MSG_SIZ) && appData.debugMode )
1102           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1103
1104         DisplayFatalError(buf, 0, 2);
1105         return;
1106
1107       case VariantUnknown:
1108       case VariantLoadable:
1109       case Variant29:
1110       case Variant30:
1111       case Variant31:
1112       case Variant32:
1113       case Variant33:
1114       case Variant34:
1115       case Variant35:
1116       case Variant36:
1117       default:
1118         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1119         if( (len >= MSG_SIZ) && appData.debugMode )
1120           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1121
1122         DisplayFatalError(buf, 0, 2);
1123         return;
1124
1125       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1126       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1127       case VariantGothic:     /* [HGM] should work */
1128       case VariantCapablanca: /* [HGM] should work */
1129       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1130       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1131       case VariantKnightmate: /* [HGM] should work */
1132       case VariantCylinder:   /* [HGM] untested */
1133       case VariantFalcon:     /* [HGM] untested */
1134       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1135                                  offboard interposition not understood */
1136       case VariantNormal:     /* definitely works! */
1137       case VariantWildCastle: /* pieces not automatically shuffled */
1138       case VariantNoCastle:   /* pieces not automatically shuffled */
1139       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1140       case VariantLosers:     /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantSuicide:    /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantGiveaway:   /* should work except for win condition,
1145                                  and doesn't know captures are mandatory */
1146       case VariantTwoKings:   /* should work */
1147       case VariantAtomic:     /* should work except for win condition */
1148       case Variant3Check:     /* should work except for win condition */
1149       case VariantShatranj:   /* should work except for all win conditions */
1150       case VariantMakruk:     /* should work except for draw countdown */
1151       case VariantBerolina:   /* might work if TestLegality is off */
1152       case VariantCapaRandom: /* should work */
1153       case VariantJanus:      /* should work */
1154       case VariantSuper:      /* experimental */
1155       case VariantGreat:      /* experimental, requires legality testing to be off */
1156       case VariantSChess:     /* S-Chess, should work */
1157       case VariantGrand:      /* should work */
1158       case VariantSpartan:    /* should work */
1159         break;
1160       }
1161     }
1162
1163 }
1164
1165 int
1166 NextIntegerFromString (char ** str, long * value)
1167 {
1168     int result = -1;
1169     char * s = *str;
1170
1171     while( *s == ' ' || *s == '\t' ) {
1172         s++;
1173     }
1174
1175     *value = 0;
1176
1177     if( *s >= '0' && *s <= '9' ) {
1178         while( *s >= '0' && *s <= '9' ) {
1179             *value = *value * 10 + (*s - '0');
1180             s++;
1181         }
1182
1183         result = 0;
1184     }
1185
1186     *str = s;
1187
1188     return result;
1189 }
1190
1191 int
1192 NextTimeControlFromString (char ** str, long * value)
1193 {
1194     long temp;
1195     int result = NextIntegerFromString( str, &temp );
1196
1197     if( result == 0 ) {
1198         *value = temp * 60; /* Minutes */
1199         if( **str == ':' ) {
1200             (*str)++;
1201             result = NextIntegerFromString( str, &temp );
1202             *value += temp; /* Seconds */
1203         }
1204     }
1205
1206     return result;
1207 }
1208
1209 int
1210 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1211 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1212     int result = -1, type = 0; long temp, temp2;
1213
1214     if(**str != ':') return -1; // old params remain in force!
1215     (*str)++;
1216     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1217     if( NextIntegerFromString( str, &temp ) ) return -1;
1218     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1219
1220     if(**str != '/') {
1221         /* time only: incremental or sudden-death time control */
1222         if(**str == '+') { /* increment follows; read it */
1223             (*str)++;
1224             if(**str == '!') type = *(*str)++; // Bronstein TC
1225             if(result = NextIntegerFromString( str, &temp2)) return -1;
1226             *inc = temp2 * 1000;
1227             if(**str == '.') { // read fraction of increment
1228                 char *start = ++(*str);
1229                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1230                 temp2 *= 1000;
1231                 while(start++ < *str) temp2 /= 10;
1232                 *inc += temp2;
1233             }
1234         } else *inc = 0;
1235         *moves = 0; *tc = temp * 1000; *incType = type;
1236         return 0;
1237     }
1238
1239     (*str)++; /* classical time control */
1240     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1241
1242     if(result == 0) {
1243         *moves = temp;
1244         *tc    = temp2 * 1000;
1245         *inc   = 0;
1246         *incType = type;
1247     }
1248     return result;
1249 }
1250
1251 int
1252 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1253 {   /* [HGM] get time to add from the multi-session time-control string */
1254     int incType, moves=1; /* kludge to force reading of first session */
1255     long time, increment;
1256     char *s = tcString;
1257
1258     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1259     do {
1260         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1261         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1262         if(movenr == -1) return time;    /* last move before new session     */
1263         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1264         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1265         if(!moves) return increment;     /* current session is incremental   */
1266         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1267     } while(movenr >= -1);               /* try again for next session       */
1268
1269     return 0; // no new time quota on this move
1270 }
1271
1272 int
1273 ParseTimeControl (char *tc, float ti, int mps)
1274 {
1275   long tc1;
1276   long tc2;
1277   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1278   int min, sec=0;
1279
1280   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1281   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1282       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1283   if(ti > 0) {
1284
1285     if(mps)
1286       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1287     else 
1288       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1289   } else {
1290     if(mps)
1291       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1292     else 
1293       snprintf(buf, MSG_SIZ, ":%s", mytc);
1294   }
1295   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1296   
1297   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1298     return FALSE;
1299   }
1300
1301   if( *tc == '/' ) {
1302     /* Parse second time control */
1303     tc++;
1304
1305     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1306       return FALSE;
1307     }
1308
1309     if( tc2 == 0 ) {
1310       return FALSE;
1311     }
1312
1313     timeControl_2 = tc2 * 1000;
1314   }
1315   else {
1316     timeControl_2 = 0;
1317   }
1318
1319   if( tc1 == 0 ) {
1320     return FALSE;
1321   }
1322
1323   timeControl = tc1 * 1000;
1324
1325   if (ti >= 0) {
1326     timeIncrement = ti * 1000;  /* convert to ms */
1327     movesPerSession = 0;
1328   } else {
1329     timeIncrement = 0;
1330     movesPerSession = mps;
1331   }
1332   return TRUE;
1333 }
1334
1335 void
1336 InitBackEnd2 ()
1337 {
1338     if (appData.debugMode) {
1339         fprintf(debugFP, "%s\n", programVersion);
1340     }
1341     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1342
1343     set_cont_sequence(appData.wrapContSeq);
1344     if (appData.matchGames > 0) {
1345         appData.matchMode = TRUE;
1346     } else if (appData.matchMode) {
1347         appData.matchGames = 1;
1348     }
1349     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1350         appData.matchGames = appData.sameColorGames;
1351     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1352         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1353         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1354     }
1355     Reset(TRUE, FALSE);
1356     if (appData.noChessProgram || first.protocolVersion == 1) {
1357       InitBackEnd3();
1358     } else {
1359       /* kludge: allow timeout for initial "feature" commands */
1360       FreezeUI();
1361       DisplayMessage("", _("Starting chess program"));
1362       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1363     }
1364 }
1365
1366 int
1367 CalculateIndex (int index, int gameNr)
1368 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1369     int res;
1370     if(index > 0) return index; // fixed nmber
1371     if(index == 0) return 1;
1372     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1373     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1374     return res;
1375 }
1376
1377 int
1378 LoadGameOrPosition (int gameNr)
1379 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1380     if (*appData.loadGameFile != NULLCHAR) {
1381         if (!LoadGameFromFile(appData.loadGameFile,
1382                 CalculateIndex(appData.loadGameIndex, gameNr),
1383                               appData.loadGameFile, FALSE)) {
1384             DisplayFatalError(_("Bad game file"), 0, 1);
1385             return 0;
1386         }
1387     } else if (*appData.loadPositionFile != NULLCHAR) {
1388         if (!LoadPositionFromFile(appData.loadPositionFile,
1389                 CalculateIndex(appData.loadPositionIndex, gameNr),
1390                                   appData.loadPositionFile)) {
1391             DisplayFatalError(_("Bad position file"), 0, 1);
1392             return 0;
1393         }
1394     }
1395     return 1;
1396 }
1397
1398 void
1399 ReserveGame (int gameNr, char resChar)
1400 {
1401     FILE *tf = fopen(appData.tourneyFile, "r+");
1402     char *p, *q, c, buf[MSG_SIZ];
1403     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1404     safeStrCpy(buf, lastMsg, MSG_SIZ);
1405     DisplayMessage(_("Pick new game"), "");
1406     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1407     ParseArgsFromFile(tf);
1408     p = q = appData.results;
1409     if(appData.debugMode) {
1410       char *r = appData.participants;
1411       fprintf(debugFP, "results = '%s'\n", p);
1412       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1413       fprintf(debugFP, "\n");
1414     }
1415     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1416     nextGame = q - p;
1417     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1418     safeStrCpy(q, p, strlen(p) + 2);
1419     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1420     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1421     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1422         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1423         q[nextGame] = '*';
1424     }
1425     fseek(tf, -(strlen(p)+4), SEEK_END);
1426     c = fgetc(tf);
1427     if(c != '"') // depending on DOS or Unix line endings we can be one off
1428          fseek(tf, -(strlen(p)+2), SEEK_END);
1429     else fseek(tf, -(strlen(p)+3), SEEK_END);
1430     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1431     DisplayMessage(buf, "");
1432     free(p); appData.results = q;
1433     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1434        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1435       int round = appData.defaultMatchGames * appData.tourneyType;
1436       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1437          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1438         UnloadEngine(&first);  // next game belongs to other pairing;
1439         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1440     }
1441     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1442 }
1443
1444 void
1445 MatchEvent (int mode)
1446 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1447         int dummy;
1448         if(matchMode) { // already in match mode: switch it off
1449             abortMatch = TRUE;
1450             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1451             return;
1452         }
1453 //      if(gameMode != BeginningOfGame) {
1454 //          DisplayError(_("You can only start a match from the initial position."), 0);
1455 //          return;
1456 //      }
1457         abortMatch = FALSE;
1458         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1459         /* Set up machine vs. machine match */
1460         nextGame = 0;
1461         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1462         if(appData.tourneyFile[0]) {
1463             ReserveGame(-1, 0);
1464             if(nextGame > appData.matchGames) {
1465                 char buf[MSG_SIZ];
1466                 if(strchr(appData.results, '*') == NULL) {
1467                     FILE *f;
1468                     appData.tourneyCycles++;
1469                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1470                         fclose(f);
1471                         NextTourneyGame(-1, &dummy);
1472                         ReserveGame(-1, 0);
1473                         if(nextGame <= appData.matchGames) {
1474                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1475                             matchMode = mode;
1476                             ScheduleDelayedEvent(NextMatchGame, 10000);
1477                             return;
1478                         }
1479                     }
1480                 }
1481                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1482                 DisplayError(buf, 0);
1483                 appData.tourneyFile[0] = 0;
1484                 return;
1485             }
1486         } else
1487         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1488             DisplayFatalError(_("Can't have a match with no chess programs"),
1489                               0, 2);
1490             return;
1491         }
1492         matchMode = mode;
1493         matchGame = roundNr = 1;
1494         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1495         NextMatchGame();
1496 }
1497
1498 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1499
1500 void
1501 InitBackEnd3 P((void))
1502 {
1503     GameMode initialMode;
1504     char buf[MSG_SIZ];
1505     int err, len;
1506
1507     InitChessProgram(&first, startedFromSetupPosition);
1508
1509     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1510         free(programVersion);
1511         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1512         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1513         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1514     }
1515
1516     if (appData.icsActive) {
1517 #ifdef WIN32
1518         /* [DM] Make a console window if needed [HGM] merged ifs */
1519         ConsoleCreate();
1520 #endif
1521         err = establish();
1522         if (err != 0)
1523           {
1524             if (*appData.icsCommPort != NULLCHAR)
1525               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1526                              appData.icsCommPort);
1527             else
1528               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1529                         appData.icsHost, appData.icsPort);
1530
1531             if( (len >= MSG_SIZ) && appData.debugMode )
1532               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1533
1534             DisplayFatalError(buf, err, 1);
1535             return;
1536         }
1537         SetICSMode();
1538         telnetISR =
1539           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1540         fromUserISR =
1541           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1542         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1543             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1544     } else if (appData.noChessProgram) {
1545         SetNCPMode();
1546     } else {
1547         SetGNUMode();
1548     }
1549
1550     if (*appData.cmailGameName != NULLCHAR) {
1551         SetCmailMode();
1552         OpenLoopback(&cmailPR);
1553         cmailISR =
1554           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1555     }
1556
1557     ThawUI();
1558     DisplayMessage("", "");
1559     if (StrCaseCmp(appData.initialMode, "") == 0) {
1560       initialMode = BeginningOfGame;
1561       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1562         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1563         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1564         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1565         ModeHighlight();
1566       }
1567     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1568       initialMode = TwoMachinesPlay;
1569     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1570       initialMode = AnalyzeFile;
1571     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1572       initialMode = AnalyzeMode;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1574       initialMode = MachinePlaysWhite;
1575     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1576       initialMode = MachinePlaysBlack;
1577     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1578       initialMode = EditGame;
1579     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1580       initialMode = EditPosition;
1581     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1582       initialMode = Training;
1583     } else {
1584       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1585       if( (len >= MSG_SIZ) && appData.debugMode )
1586         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1587
1588       DisplayFatalError(buf, 0, 2);
1589       return;
1590     }
1591
1592     if (appData.matchMode) {
1593         if(appData.tourneyFile[0]) { // start tourney from command line
1594             FILE *f;
1595             if(f = fopen(appData.tourneyFile, "r")) {
1596                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1597                 fclose(f);
1598                 appData.clockMode = TRUE;
1599                 SetGNUMode();
1600             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1601         }
1602         MatchEvent(TRUE);
1603     } else if (*appData.cmailGameName != NULLCHAR) {
1604         /* Set up cmail mode */
1605         ReloadCmailMsgEvent(TRUE);
1606     } else {
1607         /* Set up other modes */
1608         if (initialMode == AnalyzeFile) {
1609           if (*appData.loadGameFile == NULLCHAR) {
1610             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1611             return;
1612           }
1613         }
1614         if (*appData.loadGameFile != NULLCHAR) {
1615             (void) LoadGameFromFile(appData.loadGameFile,
1616                                     appData.loadGameIndex,
1617                                     appData.loadGameFile, TRUE);
1618         } else if (*appData.loadPositionFile != NULLCHAR) {
1619             (void) LoadPositionFromFile(appData.loadPositionFile,
1620                                         appData.loadPositionIndex,
1621                                         appData.loadPositionFile);
1622             /* [HGM] try to make self-starting even after FEN load */
1623             /* to allow automatic setup of fairy variants with wtm */
1624             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1625                 gameMode = BeginningOfGame;
1626                 setboardSpoiledMachineBlack = 1;
1627             }
1628             /* [HGM] loadPos: make that every new game uses the setup */
1629             /* from file as long as we do not switch variant          */
1630             if(!blackPlaysFirst) {
1631                 startedFromPositionFile = TRUE;
1632                 CopyBoard(filePosition, boards[0]);
1633             }
1634         }
1635         if (initialMode == AnalyzeMode) {
1636           if (appData.noChessProgram) {
1637             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1638             return;
1639           }
1640           if (appData.icsActive) {
1641             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1642             return;
1643           }
1644           AnalyzeModeEvent();
1645         } else if (initialMode == AnalyzeFile) {
1646           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1647           ShowThinkingEvent();
1648           AnalyzeFileEvent();
1649           AnalysisPeriodicEvent(1);
1650         } else if (initialMode == MachinePlaysWhite) {
1651           if (appData.noChessProgram) {
1652             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1653                               0, 2);
1654             return;
1655           }
1656           if (appData.icsActive) {
1657             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1658                               0, 2);
1659             return;
1660           }
1661           MachineWhiteEvent();
1662         } else if (initialMode == MachinePlaysBlack) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1665                               0, 2);
1666             return;
1667           }
1668           if (appData.icsActive) {
1669             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1670                               0, 2);
1671             return;
1672           }
1673           MachineBlackEvent();
1674         } else if (initialMode == TwoMachinesPlay) {
1675           if (appData.noChessProgram) {
1676             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1677                               0, 2);
1678             return;
1679           }
1680           if (appData.icsActive) {
1681             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1682                               0, 2);
1683             return;
1684           }
1685           TwoMachinesEvent();
1686         } else if (initialMode == EditGame) {
1687           EditGameEvent();
1688         } else if (initialMode == EditPosition) {
1689           EditPositionEvent();
1690         } else if (initialMode == Training) {
1691           if (*appData.loadGameFile == NULLCHAR) {
1692             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1693             return;
1694           }
1695           TrainingEvent();
1696         }
1697     }
1698 }
1699
1700 void
1701 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1702 {
1703     DisplayBook(current+1);
1704
1705     MoveHistorySet( movelist, first, last, current, pvInfoList );
1706
1707     EvalGraphSet( first, last, current, pvInfoList );
1708
1709     MakeEngineOutputTitle();
1710 }
1711
1712 /*
1713  * Establish will establish a contact to a remote host.port.
1714  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1715  *  used to talk to the host.
1716  * Returns 0 if okay, error code if not.
1717  */
1718 int
1719 establish ()
1720 {
1721     char buf[MSG_SIZ];
1722
1723     if (*appData.icsCommPort != NULLCHAR) {
1724         /* Talk to the host through a serial comm port */
1725         return OpenCommPort(appData.icsCommPort, &icsPR);
1726
1727     } else if (*appData.gateway != NULLCHAR) {
1728         if (*appData.remoteShell == NULLCHAR) {
1729             /* Use the rcmd protocol to run telnet program on a gateway host */
1730             snprintf(buf, sizeof(buf), "%s %s %s",
1731                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1732             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1733
1734         } else {
1735             /* Use the rsh program to run telnet program on a gateway host */
1736             if (*appData.remoteUser == NULLCHAR) {
1737                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1738                         appData.gateway, appData.telnetProgram,
1739                         appData.icsHost, appData.icsPort);
1740             } else {
1741                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1742                         appData.remoteShell, appData.gateway,
1743                         appData.remoteUser, appData.telnetProgram,
1744                         appData.icsHost, appData.icsPort);
1745             }
1746             return StartChildProcess(buf, "", &icsPR);
1747
1748         }
1749     } else if (appData.useTelnet) {
1750         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1751
1752     } else {
1753         /* TCP socket interface differs somewhat between
1754            Unix and NT; handle details in the front end.
1755            */
1756         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1757     }
1758 }
1759
1760 void
1761 EscapeExpand (char *p, char *q)
1762 {       // [HGM] initstring: routine to shape up string arguments
1763         while(*p++ = *q++) if(p[-1] == '\\')
1764             switch(*q++) {
1765                 case 'n': p[-1] = '\n'; break;
1766                 case 'r': p[-1] = '\r'; break;
1767                 case 't': p[-1] = '\t'; break;
1768                 case '\\': p[-1] = '\\'; break;
1769                 case 0: *p = 0; return;
1770                 default: p[-1] = q[-1]; break;
1771             }
1772 }
1773
1774 void
1775 show_bytes (FILE *fp, char *buf, int count)
1776 {
1777     while (count--) {
1778         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1779             fprintf(fp, "\\%03o", *buf & 0xff);
1780         } else {
1781             putc(*buf, fp);
1782         }
1783         buf++;
1784     }
1785     fflush(fp);
1786 }
1787
1788 /* Returns an errno value */
1789 int
1790 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1791 {
1792     char buf[8192], *p, *q, *buflim;
1793     int left, newcount, outcount;
1794
1795     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1796         *appData.gateway != NULLCHAR) {
1797         if (appData.debugMode) {
1798             fprintf(debugFP, ">ICS: ");
1799             show_bytes(debugFP, message, count);
1800             fprintf(debugFP, "\n");
1801         }
1802         return OutputToProcess(pr, message, count, outError);
1803     }
1804
1805     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1806     p = message;
1807     q = buf;
1808     left = count;
1809     newcount = 0;
1810     while (left) {
1811         if (q >= buflim) {
1812             if (appData.debugMode) {
1813                 fprintf(debugFP, ">ICS: ");
1814                 show_bytes(debugFP, buf, newcount);
1815                 fprintf(debugFP, "\n");
1816             }
1817             outcount = OutputToProcess(pr, buf, newcount, outError);
1818             if (outcount < newcount) return -1; /* to be sure */
1819             q = buf;
1820             newcount = 0;
1821         }
1822         if (*p == '\n') {
1823             *q++ = '\r';
1824             newcount++;
1825         } else if (((unsigned char) *p) == TN_IAC) {
1826             *q++ = (char) TN_IAC;
1827             newcount ++;
1828         }
1829         *q++ = *p++;
1830         newcount++;
1831         left--;
1832     }
1833     if (appData.debugMode) {
1834         fprintf(debugFP, ">ICS: ");
1835         show_bytes(debugFP, buf, newcount);
1836         fprintf(debugFP, "\n");
1837     }
1838     outcount = OutputToProcess(pr, buf, newcount, outError);
1839     if (outcount < newcount) return -1; /* to be sure */
1840     return count;
1841 }
1842
1843 void
1844 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1845 {
1846     int outError, outCount;
1847     static int gotEof = 0;
1848     static FILE *ini;
1849
1850     /* Pass data read from player on to ICS */
1851     if (count > 0) {
1852         gotEof = 0;
1853         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1854         if (outCount < count) {
1855             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856         }
1857         if(have_sent_ICS_logon == 2) {
1858           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1859             fprintf(ini, "%s", message);
1860             have_sent_ICS_logon = 3;
1861           } else
1862             have_sent_ICS_logon = 1;
1863         } else if(have_sent_ICS_logon == 3) {
1864             fprintf(ini, "%s", message);
1865             fclose(ini);
1866           have_sent_ICS_logon = 1;
1867         }
1868     } else if (count < 0) {
1869         RemoveInputSource(isr);
1870         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1871     } else if (gotEof++ > 0) {
1872         RemoveInputSource(isr);
1873         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1874     }
1875 }
1876
1877 void
1878 KeepAlive ()
1879 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1880     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1881     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1882     SendToICS("date\n");
1883     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1884 }
1885
1886 /* added routine for printf style output to ics */
1887 void
1888 ics_printf (char *format, ...)
1889 {
1890     char buffer[MSG_SIZ];
1891     va_list args;
1892
1893     va_start(args, format);
1894     vsnprintf(buffer, sizeof(buffer), format, args);
1895     buffer[sizeof(buffer)-1] = '\0';
1896     SendToICS(buffer);
1897     va_end(args);
1898 }
1899
1900 void
1901 SendToICS (char *s)
1902 {
1903     int count, outCount, outError;
1904
1905     if (icsPR == NoProc) return;
1906
1907     count = strlen(s);
1908     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1909     if (outCount < count) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 /* This is used for sending logon scripts to the ICS. Sending
1915    without a delay causes problems when using timestamp on ICC
1916    (at least on my machine). */
1917 void
1918 SendToICSDelayed (char *s, long msdelay)
1919 {
1920     int count, outCount, outError;
1921
1922     if (icsPR == NoProc) return;
1923
1924     count = strlen(s);
1925     if (appData.debugMode) {
1926         fprintf(debugFP, ">ICS: ");
1927         show_bytes(debugFP, s, count);
1928         fprintf(debugFP, "\n");
1929     }
1930     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1931                                       msdelay);
1932     if (outCount < count) {
1933         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1934     }
1935 }
1936
1937
1938 /* Remove all highlighting escape sequences in s
1939    Also deletes any suffix starting with '('
1940    */
1941 char *
1942 StripHighlightAndTitle (char *s)
1943 {
1944     static char retbuf[MSG_SIZ];
1945     char *p = retbuf;
1946
1947     while (*s != NULLCHAR) {
1948         while (*s == '\033') {
1949             while (*s != NULLCHAR && !isalpha(*s)) s++;
1950             if (*s != NULLCHAR) s++;
1951         }
1952         while (*s != NULLCHAR && *s != '\033') {
1953             if (*s == '(' || *s == '[') {
1954                 *p = NULLCHAR;
1955                 return retbuf;
1956             }
1957             *p++ = *s++;
1958         }
1959     }
1960     *p = NULLCHAR;
1961     return retbuf;
1962 }
1963
1964 /* Remove all highlighting escape sequences in s */
1965 char *
1966 StripHighlight (char *s)
1967 {
1968     static char retbuf[MSG_SIZ];
1969     char *p = retbuf;
1970
1971     while (*s != NULLCHAR) {
1972         while (*s == '\033') {
1973             while (*s != NULLCHAR && !isalpha(*s)) s++;
1974             if (*s != NULLCHAR) s++;
1975         }
1976         while (*s != NULLCHAR && *s != '\033') {
1977             *p++ = *s++;
1978         }
1979     }
1980     *p = NULLCHAR;
1981     return retbuf;
1982 }
1983
1984 char *variantNames[] = VARIANT_NAMES;
1985 char *
1986 VariantName (VariantClass v)
1987 {
1988     return variantNames[v];
1989 }
1990
1991
1992 /* Identify a variant from the strings the chess servers use or the
1993    PGN Variant tag names we use. */
1994 VariantClass
1995 StringToVariant (char *e)
1996 {
1997     char *p;
1998     int wnum = -1;
1999     VariantClass v = VariantNormal;
2000     int i, found = FALSE;
2001     char buf[MSG_SIZ];
2002     int len;
2003
2004     if (!e) return v;
2005
2006     /* [HGM] skip over optional board-size prefixes */
2007     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2008         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2009         while( *e++ != '_');
2010     }
2011
2012     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2013         v = VariantNormal;
2014         found = TRUE;
2015     } else
2016     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2017       if (StrCaseStr(e, variantNames[i])) {
2018         v = (VariantClass) i;
2019         found = TRUE;
2020         break;
2021       }
2022     }
2023
2024     if (!found) {
2025       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2026           || StrCaseStr(e, "wild/fr")
2027           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2028         v = VariantFischeRandom;
2029       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2030                  (i = 1, p = StrCaseStr(e, "w"))) {
2031         p += i;
2032         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2033         if (isdigit(*p)) {
2034           wnum = atoi(p);
2035         } else {
2036           wnum = -1;
2037         }
2038         switch (wnum) {
2039         case 0: /* FICS only, actually */
2040         case 1:
2041           /* Castling legal even if K starts on d-file */
2042           v = VariantWildCastle;
2043           break;
2044         case 2:
2045         case 3:
2046         case 4:
2047           /* Castling illegal even if K & R happen to start in
2048              normal positions. */
2049           v = VariantNoCastle;
2050           break;
2051         case 5:
2052         case 7:
2053         case 8:
2054         case 10:
2055         case 11:
2056         case 12:
2057         case 13:
2058         case 14:
2059         case 15:
2060         case 18:
2061         case 19:
2062           /* Castling legal iff K & R start in normal positions */
2063           v = VariantNormal;
2064           break;
2065         case 6:
2066         case 20:
2067         case 21:
2068           /* Special wilds for position setup; unclear what to do here */
2069           v = VariantLoadable;
2070           break;
2071         case 9:
2072           /* Bizarre ICC game */
2073           v = VariantTwoKings;
2074           break;
2075         case 16:
2076           v = VariantKriegspiel;
2077           break;
2078         case 17:
2079           v = VariantLosers;
2080           break;
2081         case 22:
2082           v = VariantFischeRandom;
2083           break;
2084         case 23:
2085           v = VariantCrazyhouse;
2086           break;
2087         case 24:
2088           v = VariantBughouse;
2089           break;
2090         case 25:
2091           v = Variant3Check;
2092           break;
2093         case 26:
2094           /* Not quite the same as FICS suicide! */
2095           v = VariantGiveaway;
2096           break;
2097         case 27:
2098           v = VariantAtomic;
2099           break;
2100         case 28:
2101           v = VariantShatranj;
2102           break;
2103
2104         /* Temporary names for future ICC types.  The name *will* change in
2105            the next xboard/WinBoard release after ICC defines it. */
2106         case 29:
2107           v = Variant29;
2108           break;
2109         case 30:
2110           v = Variant30;
2111           break;
2112         case 31:
2113           v = Variant31;
2114           break;
2115         case 32:
2116           v = Variant32;
2117           break;
2118         case 33:
2119           v = Variant33;
2120           break;
2121         case 34:
2122           v = Variant34;
2123           break;
2124         case 35:
2125           v = Variant35;
2126           break;
2127         case 36:
2128           v = Variant36;
2129           break;
2130         case 37:
2131           v = VariantShogi;
2132           break;
2133         case 38:
2134           v = VariantXiangqi;
2135           break;
2136         case 39:
2137           v = VariantCourier;
2138           break;
2139         case 40:
2140           v = VariantGothic;
2141           break;
2142         case 41:
2143           v = VariantCapablanca;
2144           break;
2145         case 42:
2146           v = VariantKnightmate;
2147           break;
2148         case 43:
2149           v = VariantFairy;
2150           break;
2151         case 44:
2152           v = VariantCylinder;
2153           break;
2154         case 45:
2155           v = VariantFalcon;
2156           break;
2157         case 46:
2158           v = VariantCapaRandom;
2159           break;
2160         case 47:
2161           v = VariantBerolina;
2162           break;
2163         case 48:
2164           v = VariantJanus;
2165           break;
2166         case 49:
2167           v = VariantSuper;
2168           break;
2169         case 50:
2170           v = VariantGreat;
2171           break;
2172         case -1:
2173           /* Found "wild" or "w" in the string but no number;
2174              must assume it's normal chess. */
2175           v = VariantNormal;
2176           break;
2177         default:
2178           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2179           if( (len >= MSG_SIZ) && appData.debugMode )
2180             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2181
2182           DisplayError(buf, 0);
2183           v = VariantUnknown;
2184           break;
2185         }
2186       }
2187     }
2188     if (appData.debugMode) {
2189       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2190               e, wnum, VariantName(v));
2191     }
2192     return v;
2193 }
2194
2195 static int leftover_start = 0, leftover_len = 0;
2196 char star_match[STAR_MATCH_N][MSG_SIZ];
2197
2198 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2199    advance *index beyond it, and set leftover_start to the new value of
2200    *index; else return FALSE.  If pattern contains the character '*', it
2201    matches any sequence of characters not containing '\r', '\n', or the
2202    character following the '*' (if any), and the matched sequence(s) are
2203    copied into star_match.
2204    */
2205 int
2206 looking_at ( char *buf, int *index, char *pattern)
2207 {
2208     char *bufp = &buf[*index], *patternp = pattern;
2209     int star_count = 0;
2210     char *matchp = star_match[0];
2211
2212     for (;;) {
2213         if (*patternp == NULLCHAR) {
2214             *index = leftover_start = bufp - buf;
2215             *matchp = NULLCHAR;
2216             return TRUE;
2217         }
2218         if (*bufp == NULLCHAR) return FALSE;
2219         if (*patternp == '*') {
2220             if (*bufp == *(patternp + 1)) {
2221                 *matchp = NULLCHAR;
2222                 matchp = star_match[++star_count];
2223                 patternp += 2;
2224                 bufp++;
2225                 continue;
2226             } else if (*bufp == '\n' || *bufp == '\r') {
2227                 patternp++;
2228                 if (*patternp == NULLCHAR)
2229                   continue;
2230                 else
2231                   return FALSE;
2232             } else {
2233                 *matchp++ = *bufp++;
2234                 continue;
2235             }
2236         }
2237         if (*patternp != *bufp) return FALSE;
2238         patternp++;
2239         bufp++;
2240     }
2241 }
2242
2243 void
2244 SendToPlayer (char *data, int length)
2245 {
2246     int error, outCount;
2247     outCount = OutputToProcess(NoProc, data, length, &error);
2248     if (outCount < length) {
2249         DisplayFatalError(_("Error writing to display"), error, 1);
2250     }
2251 }
2252
2253 void
2254 PackHolding (char packed[], char *holding)
2255 {
2256     char *p = holding;
2257     char *q = packed;
2258     int runlength = 0;
2259     int curr = 9999;
2260     do {
2261         if (*p == curr) {
2262             runlength++;
2263         } else {
2264             switch (runlength) {
2265               case 0:
2266                 break;
2267               case 1:
2268                 *q++ = curr;
2269                 break;
2270               case 2:
2271                 *q++ = curr;
2272                 *q++ = curr;
2273                 break;
2274               default:
2275                 sprintf(q, "%d", runlength);
2276                 while (*q) q++;
2277                 *q++ = curr;
2278                 break;
2279             }
2280             runlength = 1;
2281             curr = *p;
2282         }
2283     } while (*p++);
2284     *q = NULLCHAR;
2285 }
2286
2287 /* Telnet protocol requests from the front end */
2288 void
2289 TelnetRequest (unsigned char ddww, unsigned char option)
2290 {
2291     unsigned char msg[3];
2292     int outCount, outError;
2293
2294     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2295
2296     if (appData.debugMode) {
2297         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2298         switch (ddww) {
2299           case TN_DO:
2300             ddwwStr = "DO";
2301             break;
2302           case TN_DONT:
2303             ddwwStr = "DONT";
2304             break;
2305           case TN_WILL:
2306             ddwwStr = "WILL";
2307             break;
2308           case TN_WONT:
2309             ddwwStr = "WONT";
2310             break;
2311           default:
2312             ddwwStr = buf1;
2313             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2314             break;
2315         }
2316         switch (option) {
2317           case TN_ECHO:
2318             optionStr = "ECHO";
2319             break;
2320           default:
2321             optionStr = buf2;
2322             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2323             break;
2324         }
2325         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2326     }
2327     msg[0] = TN_IAC;
2328     msg[1] = ddww;
2329     msg[2] = option;
2330     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2331     if (outCount < 3) {
2332         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2333     }
2334 }
2335
2336 void
2337 DoEcho ()
2338 {
2339     if (!appData.icsActive) return;
2340     TelnetRequest(TN_DO, TN_ECHO);
2341 }
2342
2343 void
2344 DontEcho ()
2345 {
2346     if (!appData.icsActive) return;
2347     TelnetRequest(TN_DONT, TN_ECHO);
2348 }
2349
2350 void
2351 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2352 {
2353     /* put the holdings sent to us by the server on the board holdings area */
2354     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2355     char p;
2356     ChessSquare piece;
2357
2358     if(gameInfo.holdingsWidth < 2)  return;
2359     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2360         return; // prevent overwriting by pre-board holdings
2361
2362     if( (int)lowestPiece >= BlackPawn ) {
2363         holdingsColumn = 0;
2364         countsColumn = 1;
2365         holdingsStartRow = BOARD_HEIGHT-1;
2366         direction = -1;
2367     } else {
2368         holdingsColumn = BOARD_WIDTH-1;
2369         countsColumn = BOARD_WIDTH-2;
2370         holdingsStartRow = 0;
2371         direction = 1;
2372     }
2373
2374     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2375         board[i][holdingsColumn] = EmptySquare;
2376         board[i][countsColumn]   = (ChessSquare) 0;
2377     }
2378     while( (p=*holdings++) != NULLCHAR ) {
2379         piece = CharToPiece( ToUpper(p) );
2380         if(piece == EmptySquare) continue;
2381         /*j = (int) piece - (int) WhitePawn;*/
2382         j = PieceToNumber(piece);
2383         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2384         if(j < 0) continue;               /* should not happen */
2385         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2386         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2387         board[holdingsStartRow+j*direction][countsColumn]++;
2388     }
2389 }
2390
2391
2392 void
2393 VariantSwitch (Board board, VariantClass newVariant)
2394 {
2395    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2396    static Board oldBoard;
2397
2398    startedFromPositionFile = FALSE;
2399    if(gameInfo.variant == newVariant) return;
2400
2401    /* [HGM] This routine is called each time an assignment is made to
2402     * gameInfo.variant during a game, to make sure the board sizes
2403     * are set to match the new variant. If that means adding or deleting
2404     * holdings, we shift the playing board accordingly
2405     * This kludge is needed because in ICS observe mode, we get boards
2406     * of an ongoing game without knowing the variant, and learn about the
2407     * latter only later. This can be because of the move list we requested,
2408     * in which case the game history is refilled from the beginning anyway,
2409     * but also when receiving holdings of a crazyhouse game. In the latter
2410     * case we want to add those holdings to the already received position.
2411     */
2412
2413
2414    if (appData.debugMode) {
2415      fprintf(debugFP, "Switch board from %s to %s\n",
2416              VariantName(gameInfo.variant), VariantName(newVariant));
2417      setbuf(debugFP, NULL);
2418    }
2419    shuffleOpenings = 0;       /* [HGM] shuffle */
2420    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2421    switch(newVariant)
2422      {
2423      case VariantShogi:
2424        newWidth = 9;  newHeight = 9;
2425        gameInfo.holdingsSize = 7;
2426      case VariantBughouse:
2427      case VariantCrazyhouse:
2428        newHoldingsWidth = 2; break;
2429      case VariantGreat:
2430        newWidth = 10;
2431      case VariantSuper:
2432        newHoldingsWidth = 2;
2433        gameInfo.holdingsSize = 8;
2434        break;
2435      case VariantGothic:
2436      case VariantCapablanca:
2437      case VariantCapaRandom:
2438        newWidth = 10;
2439      default:
2440        newHoldingsWidth = gameInfo.holdingsSize = 0;
2441      };
2442
2443    if(newWidth  != gameInfo.boardWidth  ||
2444       newHeight != gameInfo.boardHeight ||
2445       newHoldingsWidth != gameInfo.holdingsWidth ) {
2446
2447      /* shift position to new playing area, if needed */
2448      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2449        for(i=0; i<BOARD_HEIGHT; i++)
2450          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2451            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2452              board[i][j];
2453        for(i=0; i<newHeight; i++) {
2454          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2455          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2456        }
2457      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2458        for(i=0; i<BOARD_HEIGHT; i++)
2459          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2460            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2461              board[i][j];
2462      }
2463      board[HOLDINGS_SET] = 0;
2464      gameInfo.boardWidth  = newWidth;
2465      gameInfo.boardHeight = newHeight;
2466      gameInfo.holdingsWidth = newHoldingsWidth;
2467      gameInfo.variant = newVariant;
2468      InitDrawingSizes(-2, 0);
2469    } else gameInfo.variant = newVariant;
2470    CopyBoard(oldBoard, board);   // remember correctly formatted board
2471      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2472    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2473 }
2474
2475 static int loggedOn = FALSE;
2476
2477 /*-- Game start info cache: --*/
2478 int gs_gamenum;
2479 char gs_kind[MSG_SIZ];
2480 static char player1Name[128] = "";
2481 static char player2Name[128] = "";
2482 static char cont_seq[] = "\n\\   ";
2483 static int player1Rating = -1;
2484 static int player2Rating = -1;
2485 /*----------------------------*/
2486
2487 ColorClass curColor = ColorNormal;
2488 int suppressKibitz = 0;
2489
2490 // [HGM] seekgraph
2491 Boolean soughtPending = FALSE;
2492 Boolean seekGraphUp;
2493 #define MAX_SEEK_ADS 200
2494 #define SQUARE 0x80
2495 char *seekAdList[MAX_SEEK_ADS];
2496 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2497 float tcList[MAX_SEEK_ADS];
2498 char colorList[MAX_SEEK_ADS];
2499 int nrOfSeekAds = 0;
2500 int minRating = 1010, maxRating = 2800;
2501 int hMargin = 10, vMargin = 20, h, w;
2502 extern int squareSize, lineGap;
2503
2504 void
2505 PlotSeekAd (int i)
2506 {
2507         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2508         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2509         if(r < minRating+100 && r >=0 ) r = minRating+100;
2510         if(r > maxRating) r = maxRating;
2511         if(tc < 1.f) tc = 1.f;
2512         if(tc > 95.f) tc = 95.f;
2513         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2514         y = ((double)r - minRating)/(maxRating - minRating)
2515             * (h-vMargin-squareSize/8-1) + vMargin;
2516         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2517         if(strstr(seekAdList[i], " u ")) color = 1;
2518         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2519            !strstr(seekAdList[i], "bullet") &&
2520            !strstr(seekAdList[i], "blitz") &&
2521            !strstr(seekAdList[i], "standard") ) color = 2;
2522         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2523         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2524 }
2525
2526 void
2527 PlotSingleSeekAd (int i)
2528 {
2529         PlotSeekAd(i);
2530 }
2531
2532 void
2533 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2534 {
2535         char buf[MSG_SIZ], *ext = "";
2536         VariantClass v = StringToVariant(type);
2537         if(strstr(type, "wild")) {
2538             ext = type + 4; // append wild number
2539             if(v == VariantFischeRandom) type = "chess960"; else
2540             if(v == VariantLoadable) type = "setup"; else
2541             type = VariantName(v);
2542         }
2543         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2544         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2545             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2546             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2547             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2548             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2549             seekNrList[nrOfSeekAds] = nr;
2550             zList[nrOfSeekAds] = 0;
2551             seekAdList[nrOfSeekAds++] = StrSave(buf);
2552             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2553         }
2554 }
2555
2556 void
2557 EraseSeekDot (int i)
2558 {
2559     int x = xList[i], y = yList[i], d=squareSize/4, k;
2560     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2561     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2562     // now replot every dot that overlapped
2563     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2564         int xx = xList[k], yy = yList[k];
2565         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2566             DrawSeekDot(xx, yy, colorList[k]);
2567     }
2568 }
2569
2570 void
2571 RemoveSeekAd (int nr)
2572 {
2573         int i;
2574         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2575             EraseSeekDot(i);
2576             if(seekAdList[i]) free(seekAdList[i]);
2577             seekAdList[i] = seekAdList[--nrOfSeekAds];
2578             seekNrList[i] = seekNrList[nrOfSeekAds];
2579             ratingList[i] = ratingList[nrOfSeekAds];
2580             colorList[i]  = colorList[nrOfSeekAds];
2581             tcList[i] = tcList[nrOfSeekAds];
2582             xList[i]  = xList[nrOfSeekAds];
2583             yList[i]  = yList[nrOfSeekAds];
2584             zList[i]  = zList[nrOfSeekAds];
2585             seekAdList[nrOfSeekAds] = NULL;
2586             break;
2587         }
2588 }
2589
2590 Boolean
2591 MatchSoughtLine (char *line)
2592 {
2593     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2594     int nr, base, inc, u=0; char dummy;
2595
2596     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2597        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2598        (u=1) &&
2599        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2600         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2601         // match: compact and save the line
2602         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2603         return TRUE;
2604     }
2605     return FALSE;
2606 }
2607
2608 int
2609 DrawSeekGraph ()
2610 {
2611     int i;
2612     if(!seekGraphUp) return FALSE;
2613     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2614     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2615
2616     DrawSeekBackground(0, 0, w, h);
2617     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2618     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2619     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2620         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2621         yy = h-1-yy;
2622         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2623         if(i%500 == 0) {
2624             char buf[MSG_SIZ];
2625             snprintf(buf, MSG_SIZ, "%d", i);
2626             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2627         }
2628     }
2629     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2630     for(i=1; i<100; i+=(i<10?1:5)) {
2631         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2632         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2633         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2634             char buf[MSG_SIZ];
2635             snprintf(buf, MSG_SIZ, "%d", i);
2636             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2637         }
2638     }
2639     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2640     return TRUE;
2641 }
2642
2643 int
2644 SeekGraphClick (ClickType click, int x, int y, int moving)
2645 {
2646     static int lastDown = 0, displayed = 0, lastSecond;
2647     if(y < 0) return FALSE;
2648     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2649         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2650         if(!seekGraphUp) return FALSE;
2651         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2652         DrawPosition(TRUE, NULL);
2653         return TRUE;
2654     }
2655     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2656         if(click == Release || moving) return FALSE;
2657         nrOfSeekAds = 0;
2658         soughtPending = TRUE;
2659         SendToICS(ics_prefix);
2660         SendToICS("sought\n"); // should this be "sought all"?
2661     } else { // issue challenge based on clicked ad
2662         int dist = 10000; int i, closest = 0, second = 0;
2663         for(i=0; i<nrOfSeekAds; i++) {
2664             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2665             if(d < dist) { dist = d; closest = i; }
2666             second += (d - zList[i] < 120); // count in-range ads
2667             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2668         }
2669         if(dist < 120) {
2670             char buf[MSG_SIZ];
2671             second = (second > 1);
2672             if(displayed != closest || second != lastSecond) {
2673                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2674                 lastSecond = second; displayed = closest;
2675             }
2676             if(click == Press) {
2677                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2678                 lastDown = closest;
2679                 return TRUE;
2680             } // on press 'hit', only show info
2681             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2682             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2683             SendToICS(ics_prefix);
2684             SendToICS(buf);
2685             return TRUE; // let incoming board of started game pop down the graph
2686         } else if(click == Release) { // release 'miss' is ignored
2687             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2688             if(moving == 2) { // right up-click
2689                 nrOfSeekAds = 0; // refresh graph
2690                 soughtPending = TRUE;
2691                 SendToICS(ics_prefix);
2692                 SendToICS("sought\n"); // should this be "sought all"?
2693             }
2694             return TRUE;
2695         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2696         // press miss or release hit 'pop down' seek graph
2697         seekGraphUp = FALSE;
2698         DrawPosition(TRUE, NULL);
2699     }
2700     return TRUE;
2701 }
2702
2703 void
2704 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2705 {
2706 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2707 #define STARTED_NONE 0
2708 #define STARTED_MOVES 1
2709 #define STARTED_BOARD 2
2710 #define STARTED_OBSERVE 3
2711 #define STARTED_HOLDINGS 4
2712 #define STARTED_CHATTER 5
2713 #define STARTED_COMMENT 6
2714 #define STARTED_MOVES_NOHIDE 7
2715
2716     static int started = STARTED_NONE;
2717     static char parse[20000];
2718     static int parse_pos = 0;
2719     static char buf[BUF_SIZE + 1];
2720     static int firstTime = TRUE, intfSet = FALSE;
2721     static ColorClass prevColor = ColorNormal;
2722     static int savingComment = FALSE;
2723     static int cmatch = 0; // continuation sequence match
2724     char *bp;
2725     char str[MSG_SIZ];
2726     int i, oldi;
2727     int buf_len;
2728     int next_out;
2729     int tkind;
2730     int backup;    /* [DM] For zippy color lines */
2731     char *p;
2732     char talker[MSG_SIZ]; // [HGM] chat
2733     int channel;
2734
2735     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2736
2737     if (appData.debugMode) {
2738       if (!error) {
2739         fprintf(debugFP, "<ICS: ");
2740         show_bytes(debugFP, data, count);
2741         fprintf(debugFP, "\n");
2742       }
2743     }
2744
2745     if (appData.debugMode) { int f = forwardMostMove;
2746         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2747                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2748                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2749     }
2750     if (count > 0) {
2751         /* If last read ended with a partial line that we couldn't parse,
2752            prepend it to the new read and try again. */
2753         if (leftover_len > 0) {
2754             for (i=0; i<leftover_len; i++)
2755               buf[i] = buf[leftover_start + i];
2756         }
2757
2758     /* copy new characters into the buffer */
2759     bp = buf + leftover_len;
2760     buf_len=leftover_len;
2761     for (i=0; i<count; i++)
2762     {
2763         // ignore these
2764         if (data[i] == '\r')
2765             continue;
2766
2767         // join lines split by ICS?
2768         if (!appData.noJoin)
2769         {
2770             /*
2771                 Joining just consists of finding matches against the
2772                 continuation sequence, and discarding that sequence
2773                 if found instead of copying it.  So, until a match
2774                 fails, there's nothing to do since it might be the
2775                 complete sequence, and thus, something we don't want
2776                 copied.
2777             */
2778             if (data[i] == cont_seq[cmatch])
2779             {
2780                 cmatch++;
2781                 if (cmatch == strlen(cont_seq))
2782                 {
2783                     cmatch = 0; // complete match.  just reset the counter
2784
2785                     /*
2786                         it's possible for the ICS to not include the space
2787                         at the end of the last word, making our [correct]
2788                         join operation fuse two separate words.  the server
2789                         does this when the space occurs at the width setting.
2790                     */
2791                     if (!buf_len || buf[buf_len-1] != ' ')
2792                     {
2793                         *bp++ = ' ';
2794                         buf_len++;
2795                     }
2796                 }
2797                 continue;
2798             }
2799             else if (cmatch)
2800             {
2801                 /*
2802                     match failed, so we have to copy what matched before
2803                     falling through and copying this character.  In reality,
2804                     this will only ever be just the newline character, but
2805                     it doesn't hurt to be precise.
2806                 */
2807                 strncpy(bp, cont_seq, cmatch);
2808                 bp += cmatch;
2809                 buf_len += cmatch;
2810                 cmatch = 0;
2811             }
2812         }
2813
2814         // copy this char
2815         *bp++ = data[i];
2816         buf_len++;
2817     }
2818
2819         buf[buf_len] = NULLCHAR;
2820 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2821         next_out = 0;
2822         leftover_start = 0;
2823
2824         i = 0;
2825         while (i < buf_len) {
2826             /* Deal with part of the TELNET option negotiation
2827                protocol.  We refuse to do anything beyond the
2828                defaults, except that we allow the WILL ECHO option,
2829                which ICS uses to turn off password echoing when we are
2830                directly connected to it.  We reject this option
2831                if localLineEditing mode is on (always on in xboard)
2832                and we are talking to port 23, which might be a real
2833                telnet server that will try to keep WILL ECHO on permanently.
2834              */
2835             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2836                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2837                 unsigned char option;
2838                 oldi = i;
2839                 switch ((unsigned char) buf[++i]) {
2840                   case TN_WILL:
2841                     if (appData.debugMode)
2842                       fprintf(debugFP, "\n<WILL ");
2843                     switch (option = (unsigned char) buf[++i]) {
2844                       case TN_ECHO:
2845                         if (appData.debugMode)
2846                           fprintf(debugFP, "ECHO ");
2847                         /* Reply only if this is a change, according
2848                            to the protocol rules. */
2849                         if (remoteEchoOption) break;
2850                         if (appData.localLineEditing &&
2851                             atoi(appData.icsPort) == TN_PORT) {
2852                             TelnetRequest(TN_DONT, TN_ECHO);
2853                         } else {
2854                             EchoOff();
2855                             TelnetRequest(TN_DO, TN_ECHO);
2856                             remoteEchoOption = TRUE;
2857                         }
2858                         break;
2859                       default:
2860                         if (appData.debugMode)
2861                           fprintf(debugFP, "%d ", option);
2862                         /* Whatever this is, we don't want it. */
2863                         TelnetRequest(TN_DONT, option);
2864                         break;
2865                     }
2866                     break;
2867                   case TN_WONT:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<WONT ");
2870                     switch (option = (unsigned char) buf[++i]) {
2871                       case TN_ECHO:
2872                         if (appData.debugMode)
2873                           fprintf(debugFP, "ECHO ");
2874                         /* Reply only if this is a change, according
2875                            to the protocol rules. */
2876                         if (!remoteEchoOption) break;
2877                         EchoOn();
2878                         TelnetRequest(TN_DONT, TN_ECHO);
2879                         remoteEchoOption = FALSE;
2880                         break;
2881                       default:
2882                         if (appData.debugMode)
2883                           fprintf(debugFP, "%d ", (unsigned char) option);
2884                         /* Whatever this is, it must already be turned
2885                            off, because we never agree to turn on
2886                            anything non-default, so according to the
2887                            protocol rules, we don't reply. */
2888                         break;
2889                     }
2890                     break;
2891                   case TN_DO:
2892                     if (appData.debugMode)
2893                       fprintf(debugFP, "\n<DO ");
2894                     switch (option = (unsigned char) buf[++i]) {
2895                       default:
2896                         /* Whatever this is, we refuse to do it. */
2897                         if (appData.debugMode)
2898                           fprintf(debugFP, "%d ", option);
2899                         TelnetRequest(TN_WONT, option);
2900                         break;
2901                     }
2902                     break;
2903                   case TN_DONT:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<DONT ");
2906                     switch (option = (unsigned char) buf[++i]) {
2907                       default:
2908                         if (appData.debugMode)
2909                           fprintf(debugFP, "%d ", option);
2910                         /* Whatever this is, we are already not doing
2911                            it, because we never agree to do anything
2912                            non-default, so according to the protocol
2913                            rules, we don't reply. */
2914                         break;
2915                     }
2916                     break;
2917                   case TN_IAC:
2918                     if (appData.debugMode)
2919                       fprintf(debugFP, "\n<IAC ");
2920                     /* Doubled IAC; pass it through */
2921                     i--;
2922                     break;
2923                   default:
2924                     if (appData.debugMode)
2925                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2926                     /* Drop all other telnet commands on the floor */
2927                     break;
2928                 }
2929                 if (oldi > next_out)
2930                   SendToPlayer(&buf[next_out], oldi - next_out);
2931                 if (++i > next_out)
2932                   next_out = i;
2933                 continue;
2934             }
2935
2936             /* OK, this at least will *usually* work */
2937             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2938                 loggedOn = TRUE;
2939             }
2940
2941             if (loggedOn && !intfSet) {
2942                 if (ics_type == ICS_ICC) {
2943                   snprintf(str, MSG_SIZ,
2944                           "/set-quietly interface %s\n/set-quietly style 12\n",
2945                           programVersion);
2946                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2947                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2948                 } else if (ics_type == ICS_CHESSNET) {
2949                   snprintf(str, MSG_SIZ, "/style 12\n");
2950                 } else {
2951                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2952                   strcat(str, programVersion);
2953                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2954                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2955                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2956 #ifdef WIN32
2957                   strcat(str, "$iset nohighlight 1\n");
2958 #endif
2959                   strcat(str, "$iset lock 1\n$style 12\n");
2960                 }
2961                 SendToICS(str);
2962                 NotifyFrontendLogin();
2963                 intfSet = TRUE;
2964             }
2965
2966             if (started == STARTED_COMMENT) {
2967                 /* Accumulate characters in comment */
2968                 parse[parse_pos++] = buf[i];
2969                 if (buf[i] == '\n') {
2970                     parse[parse_pos] = NULLCHAR;
2971                     if(chattingPartner>=0) {
2972                         char mess[MSG_SIZ];
2973                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2974                         OutputChatMessage(chattingPartner, mess);
2975                         chattingPartner = -1;
2976                         next_out = i+1; // [HGM] suppress printing in ICS window
2977                     } else
2978                     if(!suppressKibitz) // [HGM] kibitz
2979                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2980                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2981                         int nrDigit = 0, nrAlph = 0, j;
2982                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2983                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2984                         parse[parse_pos] = NULLCHAR;
2985                         // try to be smart: if it does not look like search info, it should go to
2986                         // ICS interaction window after all, not to engine-output window.
2987                         for(j=0; j<parse_pos; j++) { // count letters and digits
2988                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2989                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2990                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2991                         }
2992                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2993                             int depth=0; float score;
2994                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2995                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2996                                 pvInfoList[forwardMostMove-1].depth = depth;
2997                                 pvInfoList[forwardMostMove-1].score = 100*score;
2998                             }
2999                             OutputKibitz(suppressKibitz, parse);
3000                         } else {
3001                             char tmp[MSG_SIZ];
3002                             if(gameMode == IcsObserving) // restore original ICS messages
3003                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3004                             else
3005                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3006                             SendToPlayer(tmp, strlen(tmp));
3007                         }
3008                         next_out = i+1; // [HGM] suppress printing in ICS window
3009                     }
3010                     started = STARTED_NONE;
3011                 } else {
3012                     /* Don't match patterns against characters in comment */
3013                     i++;
3014                     continue;
3015                 }
3016             }
3017             if (started == STARTED_CHATTER) {
3018                 if (buf[i] != '\n') {
3019                     /* Don't match patterns against characters in chatter */
3020                     i++;
3021                     continue;
3022                 }
3023                 started = STARTED_NONE;
3024                 if(suppressKibitz) next_out = i+1;
3025             }
3026
3027             /* Kludge to deal with rcmd protocol */
3028             if (firstTime && looking_at(buf, &i, "\001*")) {
3029                 DisplayFatalError(&buf[1], 0, 1);
3030                 continue;
3031             } else {
3032                 firstTime = FALSE;
3033             }
3034
3035             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3036                 ics_type = ICS_ICC;
3037                 ics_prefix = "/";
3038                 if (appData.debugMode)
3039                   fprintf(debugFP, "ics_type %d\n", ics_type);
3040                 continue;
3041             }
3042             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3043                 ics_type = ICS_FICS;
3044                 ics_prefix = "$";
3045                 if (appData.debugMode)
3046                   fprintf(debugFP, "ics_type %d\n", ics_type);
3047                 continue;
3048             }
3049             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3050                 ics_type = ICS_CHESSNET;
3051                 ics_prefix = "/";
3052                 if (appData.debugMode)
3053                   fprintf(debugFP, "ics_type %d\n", ics_type);
3054                 continue;
3055             }
3056
3057             if (!loggedOn &&
3058                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3059                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3060                  looking_at(buf, &i, "will be \"*\""))) {
3061               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3062               continue;
3063             }
3064
3065             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3066               char buf[MSG_SIZ];
3067               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3068               DisplayIcsInteractionTitle(buf);
3069               have_set_title = TRUE;
3070             }
3071
3072             /* skip finger notes */
3073             if (started == STARTED_NONE &&
3074                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3075                  (buf[i] == '1' && buf[i+1] == '0')) &&
3076                 buf[i+2] == ':' && buf[i+3] == ' ') {
3077               started = STARTED_CHATTER;
3078               i += 3;
3079               continue;
3080             }
3081
3082             oldi = i;
3083             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3084             if(appData.seekGraph) {
3085                 if(soughtPending && MatchSoughtLine(buf+i)) {
3086                     i = strstr(buf+i, "rated") - buf;
3087                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                     next_out = leftover_start = i;
3089                     started = STARTED_CHATTER;
3090                     suppressKibitz = TRUE;
3091                     continue;
3092                 }
3093                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3094                         && looking_at(buf, &i, "* ads displayed")) {
3095                     soughtPending = FALSE;
3096                     seekGraphUp = TRUE;
3097                     DrawSeekGraph();
3098                     continue;
3099                 }
3100                 if(appData.autoRefresh) {
3101                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3102                         int s = (ics_type == ICS_ICC); // ICC format differs
3103                         if(seekGraphUp)
3104                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3105                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3106                         looking_at(buf, &i, "*% "); // eat prompt
3107                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3108                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3109                         next_out = i; // suppress
3110                         continue;
3111                     }
3112                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3113                         char *p = star_match[0];
3114                         while(*p) {
3115                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3116                             while(*p && *p++ != ' '); // next
3117                         }
3118                         looking_at(buf, &i, "*% "); // eat prompt
3119                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                         next_out = i;
3121                         continue;
3122                     }
3123                 }
3124             }
3125
3126             /* skip formula vars */
3127             if (started == STARTED_NONE &&
3128                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3129               started = STARTED_CHATTER;
3130               i += 3;
3131               continue;
3132             }
3133
3134             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3135             if (appData.autoKibitz && started == STARTED_NONE &&
3136                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3137                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3138                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3139                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3140                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3141                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3142                         suppressKibitz = TRUE;
3143                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                         next_out = i;
3145                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3146                                 && (gameMode == IcsPlayingWhite)) ||
3147                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3148                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3149                             started = STARTED_CHATTER; // own kibitz we simply discard
3150                         else {
3151                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3152                             parse_pos = 0; parse[0] = NULLCHAR;
3153                             savingComment = TRUE;
3154                             suppressKibitz = gameMode != IcsObserving ? 2 :
3155                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3156                         }
3157                         continue;
3158                 } else
3159                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3160                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3161                          && atoi(star_match[0])) {
3162                     // suppress the acknowledgements of our own autoKibitz
3163                     char *p;
3164                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3165                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3166                     SendToPlayer(star_match[0], strlen(star_match[0]));
3167                     if(looking_at(buf, &i, "*% ")) // eat prompt
3168                         suppressKibitz = FALSE;
3169                     next_out = i;
3170                     continue;
3171                 }
3172             } // [HGM] kibitz: end of patch
3173
3174             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3175
3176             // [HGM] chat: intercept tells by users for which we have an open chat window
3177             channel = -1;
3178             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3179                                            looking_at(buf, &i, "* whispers:") ||
3180                                            looking_at(buf, &i, "* kibitzes:") ||
3181                                            looking_at(buf, &i, "* shouts:") ||
3182                                            looking_at(buf, &i, "* c-shouts:") ||
3183                                            looking_at(buf, &i, "--> * ") ||
3184                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3185                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3186                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3187                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3188                 int p;
3189                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3190                 chattingPartner = -1;
3191
3192                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3193                 for(p=0; p<MAX_CHAT; p++) {
3194                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3195                     talker[0] = '['; strcat(talker, "] ");
3196                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3197                     chattingPartner = p; break;
3198                     }
3199                 } else
3200                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3201                 for(p=0; p<MAX_CHAT; p++) {
3202                     if(!strcmp("kibitzes", chatPartner[p])) {
3203                         talker[0] = '['; strcat(talker, "] ");
3204                         chattingPartner = p; break;
3205                     }
3206                 } else
3207                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3208                 for(p=0; p<MAX_CHAT; p++) {
3209                     if(!strcmp("whispers", chatPartner[p])) {
3210                         talker[0] = '['; strcat(talker, "] ");
3211                         chattingPartner = p; break;
3212                     }
3213                 } else
3214                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3215                   if(buf[i-8] == '-' && buf[i-3] == 't')
3216                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3217                     if(!strcmp("c-shouts", chatPartner[p])) {
3218                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3219                         chattingPartner = p; break;
3220                     }
3221                   }
3222                   if(chattingPartner < 0)
3223                   for(p=0; p<MAX_CHAT; p++) {
3224                     if(!strcmp("shouts", chatPartner[p])) {
3225                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3226                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3227                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3228                         chattingPartner = p; break;
3229                     }
3230                   }
3231                 }
3232                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3233                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3234                     talker[0] = 0; Colorize(ColorTell, FALSE);
3235                     chattingPartner = p; break;
3236                 }
3237                 if(chattingPartner<0) i = oldi; else {
3238                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3239                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3240                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241                     started = STARTED_COMMENT;
3242                     parse_pos = 0; parse[0] = NULLCHAR;
3243                     savingComment = 3 + chattingPartner; // counts as TRUE
3244                     suppressKibitz = TRUE;
3245                     continue;
3246                 }
3247             } // [HGM] chat: end of patch
3248
3249           backup = i;
3250             if (appData.zippyTalk || appData.zippyPlay) {
3251                 /* [DM] Backup address for color zippy lines */
3252 #if ZIPPY
3253                if (loggedOn == TRUE)
3254                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3255                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3256 #endif
3257             } // [DM] 'else { ' deleted
3258                 if (
3259                     /* Regular tells and says */
3260                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3261                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3262                     looking_at(buf, &i, "* says: ") ||
3263                     /* Don't color "message" or "messages" output */
3264                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3265                     looking_at(buf, &i, "*. * at *:*: ") ||
3266                     looking_at(buf, &i, "--* (*:*): ") ||
3267                     /* Message notifications (same color as tells) */
3268                     looking_at(buf, &i, "* has left a message ") ||
3269                     looking_at(buf, &i, "* just sent you a message:\n") ||
3270                     /* Whispers and kibitzes */
3271                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3272                     looking_at(buf, &i, "* kibitzes: ") ||
3273                     /* Channel tells */
3274                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3275
3276                   if (tkind == 1 && strchr(star_match[0], ':')) {
3277                       /* Avoid "tells you:" spoofs in channels */
3278                      tkind = 3;
3279                   }
3280                   if (star_match[0][0] == NULLCHAR ||
3281                       strchr(star_match[0], ' ') ||
3282                       (tkind == 3 && strchr(star_match[1], ' '))) {
3283                     /* Reject bogus matches */
3284                     i = oldi;
3285                   } else {
3286                     if (appData.colorize) {
3287                       if (oldi > next_out) {
3288                         SendToPlayer(&buf[next_out], oldi - next_out);
3289                         next_out = oldi;
3290                       }
3291                       switch (tkind) {
3292                       case 1:
3293                         Colorize(ColorTell, FALSE);
3294                         curColor = ColorTell;
3295                         break;
3296                       case 2:
3297                         Colorize(ColorKibitz, FALSE);
3298                         curColor = ColorKibitz;
3299                         break;
3300                       case 3:
3301                         p = strrchr(star_match[1], '(');
3302                         if (p == NULL) {
3303                           p = star_match[1];
3304                         } else {
3305                           p++;
3306                         }
3307                         if (atoi(p) == 1) {
3308                           Colorize(ColorChannel1, FALSE);
3309                           curColor = ColorChannel1;
3310                         } else {
3311                           Colorize(ColorChannel, FALSE);
3312                           curColor = ColorChannel;
3313                         }
3314                         break;
3315                       case 5:
3316                         curColor = ColorNormal;
3317                         break;
3318                       }
3319                     }
3320                     if (started == STARTED_NONE && appData.autoComment &&
3321                         (gameMode == IcsObserving ||
3322                          gameMode == IcsPlayingWhite ||
3323                          gameMode == IcsPlayingBlack)) {
3324                       parse_pos = i - oldi;
3325                       memcpy(parse, &buf[oldi], parse_pos);
3326                       parse[parse_pos] = NULLCHAR;
3327                       started = STARTED_COMMENT;
3328                       savingComment = TRUE;
3329                     } else {
3330                       started = STARTED_CHATTER;
3331                       savingComment = FALSE;
3332                     }
3333                     loggedOn = TRUE;
3334                     continue;
3335                   }
3336                 }
3337
3338                 if (looking_at(buf, &i, "* s-shouts: ") ||
3339                     looking_at(buf, &i, "* c-shouts: ")) {
3340                     if (appData.colorize) {
3341                         if (oldi > next_out) {
3342                             SendToPlayer(&buf[next_out], oldi - next_out);
3343                             next_out = oldi;
3344                         }
3345                         Colorize(ColorSShout, FALSE);
3346                         curColor = ColorSShout;
3347                     }
3348                     loggedOn = TRUE;
3349                     started = STARTED_CHATTER;
3350                     continue;
3351                 }
3352
3353                 if (looking_at(buf, &i, "--->")) {
3354                     loggedOn = TRUE;
3355                     continue;
3356                 }
3357
3358                 if (looking_at(buf, &i, "* shouts: ") ||
3359                     looking_at(buf, &i, "--> ")) {
3360                     if (appData.colorize) {
3361                         if (oldi > next_out) {
3362                             SendToPlayer(&buf[next_out], oldi - next_out);
3363                             next_out = oldi;
3364                         }
3365                         Colorize(ColorShout, FALSE);
3366                         curColor = ColorShout;
3367                     }
3368                     loggedOn = TRUE;
3369                     started = STARTED_CHATTER;
3370                     continue;
3371                 }
3372
3373                 if (looking_at( buf, &i, "Challenge:")) {
3374                     if (appData.colorize) {
3375                         if (oldi > next_out) {
3376                             SendToPlayer(&buf[next_out], oldi - next_out);
3377                             next_out = oldi;
3378                         }
3379                         Colorize(ColorChallenge, FALSE);
3380                         curColor = ColorChallenge;
3381                     }
3382                     loggedOn = TRUE;
3383                     continue;
3384                 }
3385
3386                 if (looking_at(buf, &i, "* offers you") ||
3387                     looking_at(buf, &i, "* offers to be") ||
3388                     looking_at(buf, &i, "* would like to") ||
3389                     looking_at(buf, &i, "* requests to") ||
3390                     looking_at(buf, &i, "Your opponent offers") ||
3391                     looking_at(buf, &i, "Your opponent requests")) {
3392
3393                     if (appData.colorize) {
3394                         if (oldi > next_out) {
3395                             SendToPlayer(&buf[next_out], oldi - next_out);
3396                             next_out = oldi;
3397                         }
3398                         Colorize(ColorRequest, FALSE);
3399                         curColor = ColorRequest;
3400                     }
3401                     continue;
3402                 }
3403
3404                 if (looking_at(buf, &i, "* (*) seeking")) {
3405                     if (appData.colorize) {
3406                         if (oldi > next_out) {
3407                             SendToPlayer(&buf[next_out], oldi - next_out);
3408                             next_out = oldi;
3409                         }
3410                         Colorize(ColorSeek, FALSE);
3411                         curColor = ColorSeek;
3412                     }
3413                     continue;
3414             }
3415
3416           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3417
3418             if (looking_at(buf, &i, "\\   ")) {
3419                 if (prevColor != ColorNormal) {
3420                     if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                     }
3424                     Colorize(prevColor, TRUE);
3425                     curColor = prevColor;
3426                 }
3427                 if (savingComment) {
3428                     parse_pos = i - oldi;
3429                     memcpy(parse, &buf[oldi], parse_pos);
3430                     parse[parse_pos] = NULLCHAR;
3431                     started = STARTED_COMMENT;
3432                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3433                         chattingPartner = savingComment - 3; // kludge to remember the box
3434                 } else {
3435                     started = STARTED_CHATTER;
3436                 }
3437                 continue;
3438             }
3439
3440             if (looking_at(buf, &i, "Black Strength :") ||
3441                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3442                 looking_at(buf, &i, "<10>") ||
3443                 looking_at(buf, &i, "#@#")) {
3444                 /* Wrong board style */
3445                 loggedOn = TRUE;
3446                 SendToICS(ics_prefix);
3447                 SendToICS("set style 12\n");
3448                 SendToICS(ics_prefix);
3449                 SendToICS("refresh\n");
3450                 continue;
3451             }
3452
3453             if (looking_at(buf, &i, "login:")) {
3454               if (!have_sent_ICS_logon) {
3455                 if(ICSInitScript())
3456                   have_sent_ICS_logon = 1;
3457                 else // no init script was found
3458                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3459               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3460                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3461               }
3462                 continue;
3463             }
3464
3465             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3466                 (looking_at(buf, &i, "\n<12> ") ||
3467                  looking_at(buf, &i, "<12> "))) {
3468                 loggedOn = TRUE;
3469                 if (oldi > next_out) {
3470                     SendToPlayer(&buf[next_out], oldi - next_out);
3471                 }
3472                 next_out = i;
3473                 started = STARTED_BOARD;
3474                 parse_pos = 0;
3475                 continue;
3476             }
3477
3478             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3479                 looking_at(buf, &i, "<b1> ")) {
3480                 if (oldi > next_out) {
3481                     SendToPlayer(&buf[next_out], oldi - next_out);
3482                 }
3483                 next_out = i;
3484                 started = STARTED_HOLDINGS;
3485                 parse_pos = 0;
3486                 continue;
3487             }
3488
3489             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3490                 loggedOn = TRUE;
3491                 /* Header for a move list -- first line */
3492
3493                 switch (ics_getting_history) {
3494                   case H_FALSE:
3495                     switch (gameMode) {
3496                       case IcsIdle:
3497                       case BeginningOfGame:
3498                         /* User typed "moves" or "oldmoves" while we
3499                            were idle.  Pretend we asked for these
3500                            moves and soak them up so user can step
3501                            through them and/or save them.
3502                            */
3503                         Reset(FALSE, TRUE);
3504                         gameMode = IcsObserving;
3505                         ModeHighlight();
3506                         ics_gamenum = -1;
3507                         ics_getting_history = H_GOT_UNREQ_HEADER;
3508                         break;
3509                       case EditGame: /*?*/
3510                       case EditPosition: /*?*/
3511                         /* Should above feature work in these modes too? */
3512                         /* For now it doesn't */
3513                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3514                         break;
3515                       default:
3516                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3517                         break;
3518                     }
3519                     break;
3520                   case H_REQUESTED:
3521                     /* Is this the right one? */
3522                     if (gameInfo.white && gameInfo.black &&
3523                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3524                         strcmp(gameInfo.black, star_match[2]) == 0) {
3525                         /* All is well */
3526                         ics_getting_history = H_GOT_REQ_HEADER;
3527                     }
3528                     break;
3529                   case H_GOT_REQ_HEADER:
3530                   case H_GOT_UNREQ_HEADER:
3531                   case H_GOT_UNWANTED_HEADER:
3532                   case H_GETTING_MOVES:
3533                     /* Should not happen */
3534                     DisplayError(_("Error gathering move list: two headers"), 0);
3535                     ics_getting_history = H_FALSE;
3536                     break;
3537                 }
3538
3539                 /* Save player ratings into gameInfo if needed */
3540                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3541                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3542                     (gameInfo.whiteRating == -1 ||
3543                      gameInfo.blackRating == -1)) {
3544
3545                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3546                     gameInfo.blackRating = string_to_rating(star_match[3]);
3547                     if (appData.debugMode)
3548                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3549                               gameInfo.whiteRating, gameInfo.blackRating);
3550                 }
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i,
3555               "* * match, initial time: * minute*, increment: * second")) {
3556                 /* Header for a move list -- second line */
3557                 /* Initial board will follow if this is a wild game */
3558                 if (gameInfo.event != NULL) free(gameInfo.event);
3559                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3560                 gameInfo.event = StrSave(str);
3561                 /* [HGM] we switched variant. Translate boards if needed. */
3562                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3563                 continue;
3564             }
3565
3566             if (looking_at(buf, &i, "Move  ")) {
3567                 /* Beginning of a move list */
3568                 switch (ics_getting_history) {
3569                   case H_FALSE:
3570                     /* Normally should not happen */
3571                     /* Maybe user hit reset while we were parsing */
3572                     break;
3573                   case H_REQUESTED:
3574                     /* Happens if we are ignoring a move list that is not
3575                      * the one we just requested.  Common if the user
3576                      * tries to observe two games without turning off
3577                      * getMoveList */
3578                     break;
3579                   case H_GETTING_MOVES:
3580                     /* Should not happen */
3581                     DisplayError(_("Error gathering move list: nested"), 0);
3582                     ics_getting_history = H_FALSE;
3583                     break;
3584                   case H_GOT_REQ_HEADER:
3585                     ics_getting_history = H_GETTING_MOVES;
3586                     started = STARTED_MOVES;
3587                     parse_pos = 0;
3588                     if (oldi > next_out) {
3589                         SendToPlayer(&buf[next_out], oldi - next_out);
3590                     }
3591                     break;
3592                   case H_GOT_UNREQ_HEADER:
3593                     ics_getting_history = H_GETTING_MOVES;
3594                     started = STARTED_MOVES_NOHIDE;
3595                     parse_pos = 0;
3596                     break;
3597                   case H_GOT_UNWANTED_HEADER:
3598                     ics_getting_history = H_FALSE;
3599                     break;
3600                 }
3601                 continue;
3602             }
3603
3604             if (looking_at(buf, &i, "% ") ||
3605                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3606                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3607                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3608                     soughtPending = FALSE;
3609                     seekGraphUp = TRUE;
3610                     DrawSeekGraph();
3611                 }
3612                 if(suppressKibitz) next_out = i;
3613                 savingComment = FALSE;
3614                 suppressKibitz = 0;
3615                 switch (started) {
3616                   case STARTED_MOVES:
3617                   case STARTED_MOVES_NOHIDE:
3618                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3619                     parse[parse_pos + i - oldi] = NULLCHAR;
3620                     ParseGameHistory(parse);
3621 #if ZIPPY
3622                     if (appData.zippyPlay && first.initDone) {
3623                         FeedMovesToProgram(&first, forwardMostMove);
3624                         if (gameMode == IcsPlayingWhite) {
3625                             if (WhiteOnMove(forwardMostMove)) {
3626                                 if (first.sendTime) {
3627                                   if (first.useColors) {
3628                                     SendToProgram("black\n", &first);
3629                                   }
3630                                   SendTimeRemaining(&first, TRUE);
3631                                 }
3632                                 if (first.useColors) {
3633                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3634                                 }
3635                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3636                                 first.maybeThinking = TRUE;
3637                             } else {
3638                                 if (first.usePlayother) {
3639                                   if (first.sendTime) {
3640                                     SendTimeRemaining(&first, TRUE);
3641                                   }
3642                                   SendToProgram("playother\n", &first);
3643                                   firstMove = FALSE;
3644                                 } else {
3645                                   firstMove = TRUE;
3646                                 }
3647                             }
3648                         } else if (gameMode == IcsPlayingBlack) {
3649                             if (!WhiteOnMove(forwardMostMove)) {
3650                                 if (first.sendTime) {
3651                                   if (first.useColors) {
3652                                     SendToProgram("white\n", &first);
3653                                   }
3654                                   SendTimeRemaining(&first, FALSE);
3655                                 }
3656                                 if (first.useColors) {
3657                                   SendToProgram("black\n", &first);
3658                                 }
3659                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3660                                 first.maybeThinking = TRUE;
3661                             } else {
3662                                 if (first.usePlayother) {
3663                                   if (first.sendTime) {
3664                                     SendTimeRemaining(&first, FALSE);
3665                                   }
3666                                   SendToProgram("playother\n", &first);
3667                                   firstMove = FALSE;
3668                                 } else {
3669                                   firstMove = TRUE;
3670                                 }
3671                             }
3672                         }
3673                     }
3674 #endif
3675                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3676                         /* Moves came from oldmoves or moves command
3677                            while we weren't doing anything else.
3678                            */
3679                         currentMove = forwardMostMove;
3680                         ClearHighlights();/*!!could figure this out*/
3681                         flipView = appData.flipView;
3682                         DrawPosition(TRUE, boards[currentMove]);
3683                         DisplayBothClocks();
3684                         snprintf(str, MSG_SIZ, "%s %s %s",
3685                                 gameInfo.white, _("vs."),  gameInfo.black);
3686                         DisplayTitle(str);
3687                         gameMode = IcsIdle;
3688                     } else {
3689                         /* Moves were history of an active game */
3690                         if (gameInfo.resultDetails != NULL) {
3691                             free(gameInfo.resultDetails);
3692                             gameInfo.resultDetails = NULL;
3693                         }
3694                     }
3695                     HistorySet(parseList, backwardMostMove,
3696                                forwardMostMove, currentMove-1);
3697                     DisplayMove(currentMove - 1);
3698                     if (started == STARTED_MOVES) next_out = i;
3699                     started = STARTED_NONE;
3700                     ics_getting_history = H_FALSE;
3701                     break;
3702
3703                   case STARTED_OBSERVE:
3704                     started = STARTED_NONE;
3705                     SendToICS(ics_prefix);
3706                     SendToICS("refresh\n");
3707                     break;
3708
3709                   default:
3710                     break;
3711                 }
3712                 if(bookHit) { // [HGM] book: simulate book reply
3713                     static char bookMove[MSG_SIZ]; // a bit generous?
3714
3715                     programStats.nodes = programStats.depth = programStats.time =
3716                     programStats.score = programStats.got_only_move = 0;
3717                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3718
3719                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3720                     strcat(bookMove, bookHit);
3721                     HandleMachineMove(bookMove, &first);
3722                 }
3723                 continue;
3724             }
3725
3726             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3727                  started == STARTED_HOLDINGS ||
3728                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3729                 /* Accumulate characters in move list or board */
3730                 parse[parse_pos++] = buf[i];
3731             }
3732
3733             /* Start of game messages.  Mostly we detect start of game
3734                when the first board image arrives.  On some versions
3735                of the ICS, though, we need to do a "refresh" after starting
3736                to observe in order to get the current board right away. */
3737             if (looking_at(buf, &i, "Adding game * to observation list")) {
3738                 started = STARTED_OBSERVE;
3739                 continue;
3740             }
3741
3742             /* Handle auto-observe */
3743             if (appData.autoObserve &&
3744                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3745                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3746                 char *player;
3747                 /* Choose the player that was highlighted, if any. */
3748                 if (star_match[0][0] == '\033' ||
3749                     star_match[1][0] != '\033') {
3750                     player = star_match[0];
3751                 } else {
3752                     player = star_match[2];
3753                 }
3754                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3755                         ics_prefix, StripHighlightAndTitle(player));
3756                 SendToICS(str);
3757
3758                 /* Save ratings from notify string */
3759                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3760                 player1Rating = string_to_rating(star_match[1]);
3761                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3762                 player2Rating = string_to_rating(star_match[3]);
3763
3764                 if (appData.debugMode)
3765                   fprintf(debugFP,
3766                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3767                           player1Name, player1Rating,
3768                           player2Name, player2Rating);
3769
3770                 continue;
3771             }
3772
3773             /* Deal with automatic examine mode after a game,
3774                and with IcsObserving -> IcsExamining transition */
3775             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3776                 looking_at(buf, &i, "has made you an examiner of game *")) {
3777
3778                 int gamenum = atoi(star_match[0]);
3779                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3780                     gamenum == ics_gamenum) {
3781                     /* We were already playing or observing this game;
3782                        no need to refetch history */
3783                     gameMode = IcsExamining;
3784                     if (pausing) {
3785                         pauseExamForwardMostMove = forwardMostMove;
3786                     } else if (currentMove < forwardMostMove) {
3787                         ForwardInner(forwardMostMove);
3788                     }
3789                 } else {
3790                     /* I don't think this case really can happen */
3791                     SendToICS(ics_prefix);
3792                     SendToICS("refresh\n");
3793                 }
3794                 continue;
3795             }
3796
3797             /* Error messages */
3798 //          if (ics_user_moved) {
3799             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3800                 if (looking_at(buf, &i, "Illegal move") ||
3801                     looking_at(buf, &i, "Not a legal move") ||
3802                     looking_at(buf, &i, "Your king is in check") ||
3803                     looking_at(buf, &i, "It isn't your turn") ||
3804                     looking_at(buf, &i, "It is not your move")) {
3805                     /* Illegal move */
3806                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3807                         currentMove = forwardMostMove-1;
3808                         DisplayMove(currentMove - 1); /* before DMError */
3809                         DrawPosition(FALSE, boards[currentMove]);
3810                         SwitchClocks(forwardMostMove-1); // [HGM] race
3811                         DisplayBothClocks();
3812                     }
3813                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3814                     ics_user_moved = 0;
3815                     continue;
3816                 }
3817             }
3818
3819             if (looking_at(buf, &i, "still have time") ||
3820                 looking_at(buf, &i, "not out of time") ||
3821                 looking_at(buf, &i, "either player is out of time") ||
3822                 looking_at(buf, &i, "has timeseal; checking")) {
3823                 /* We must have called his flag a little too soon */
3824                 whiteFlag = blackFlag = FALSE;
3825                 continue;
3826             }
3827
3828             if (looking_at(buf, &i, "added * seconds to") ||
3829                 looking_at(buf, &i, "seconds were added to")) {
3830                 /* Update the clocks */
3831                 SendToICS(ics_prefix);
3832                 SendToICS("refresh\n");
3833                 continue;
3834             }
3835
3836             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3837                 ics_clock_paused = TRUE;
3838                 StopClocks();
3839                 continue;
3840             }
3841
3842             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3843                 ics_clock_paused = FALSE;
3844                 StartClocks();
3845                 continue;
3846             }
3847
3848             /* Grab player ratings from the Creating: message.
3849                Note we have to check for the special case when
3850                the ICS inserts things like [white] or [black]. */
3851             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3852                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3853                 /* star_matches:
3854                    0    player 1 name (not necessarily white)
3855                    1    player 1 rating
3856                    2    empty, white, or black (IGNORED)
3857                    3    player 2 name (not necessarily black)
3858                    4    player 2 rating
3859
3860                    The names/ratings are sorted out when the game
3861                    actually starts (below).
3862                 */
3863                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3864                 player1Rating = string_to_rating(star_match[1]);
3865                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3866                 player2Rating = string_to_rating(star_match[4]);
3867
3868                 if (appData.debugMode)
3869                   fprintf(debugFP,
3870                           "Ratings from 'Creating:' %s %d, %s %d\n",
3871                           player1Name, player1Rating,
3872                           player2Name, player2Rating);
3873
3874                 continue;
3875             }
3876
3877             /* Improved generic start/end-of-game messages */
3878             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3879                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3880                 /* If tkind == 0: */
3881                 /* star_match[0] is the game number */
3882                 /*           [1] is the white player's name */
3883                 /*           [2] is the black player's name */
3884                 /* For end-of-game: */
3885                 /*           [3] is the reason for the game end */
3886                 /*           [4] is a PGN end game-token, preceded by " " */
3887                 /* For start-of-game: */
3888                 /*           [3] begins with "Creating" or "Continuing" */
3889                 /*           [4] is " *" or empty (don't care). */
3890                 int gamenum = atoi(star_match[0]);
3891                 char *whitename, *blackname, *why, *endtoken;
3892                 ChessMove endtype = EndOfFile;
3893
3894                 if (tkind == 0) {
3895                   whitename = star_match[1];
3896                   blackname = star_match[2];
3897                   why = star_match[3];
3898                   endtoken = star_match[4];
3899                 } else {
3900                   whitename = star_match[1];
3901                   blackname = star_match[3];
3902                   why = star_match[5];
3903                   endtoken = star_match[6];
3904                 }
3905
3906                 /* Game start messages */
3907                 if (strncmp(why, "Creating ", 9) == 0 ||
3908                     strncmp(why, "Continuing ", 11) == 0) {
3909                     gs_gamenum = gamenum;
3910                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3911                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3912                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3913 #if ZIPPY
3914                     if (appData.zippyPlay) {
3915                         ZippyGameStart(whitename, blackname);
3916                     }
3917 #endif /*ZIPPY*/
3918                     partnerBoardValid = FALSE; // [HGM] bughouse
3919                     continue;
3920                 }
3921
3922                 /* Game end messages */
3923                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3924                     ics_gamenum != gamenum) {
3925                     continue;
3926                 }
3927                 while (endtoken[0] == ' ') endtoken++;
3928                 switch (endtoken[0]) {
3929                   case '*':
3930                   default:
3931                     endtype = GameUnfinished;
3932                     break;
3933                   case '0':
3934                     endtype = BlackWins;
3935                     break;
3936                   case '1':
3937                     if (endtoken[1] == '/')
3938                       endtype = GameIsDrawn;
3939                     else
3940                       endtype = WhiteWins;
3941                     break;
3942                 }
3943                 GameEnds(endtype, why, GE_ICS);
3944 #if ZIPPY
3945                 if (appData.zippyPlay && first.initDone) {
3946                     ZippyGameEnd(endtype, why);
3947                     if (first.pr == NoProc) {
3948                       /* Start the next process early so that we'll
3949                          be ready for the next challenge */
3950                       StartChessProgram(&first);
3951                     }
3952                     /* Send "new" early, in case this command takes
3953                        a long time to finish, so that we'll be ready
3954                        for the next challenge. */
3955                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3956                     Reset(TRUE, TRUE);
3957                 }
3958 #endif /*ZIPPY*/
3959                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3960                 continue;
3961             }
3962
3963             if (looking_at(buf, &i, "Removing game * from observation") ||
3964                 looking_at(buf, &i, "no longer observing game *") ||
3965                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3966                 if (gameMode == IcsObserving &&
3967                     atoi(star_match[0]) == ics_gamenum)
3968                   {
3969                       /* icsEngineAnalyze */
3970                       if (appData.icsEngineAnalyze) {
3971                             ExitAnalyzeMode();
3972                             ModeHighlight();
3973                       }
3974                       StopClocks();
3975                       gameMode = IcsIdle;
3976                       ics_gamenum = -1;
3977                       ics_user_moved = FALSE;
3978                   }
3979                 continue;
3980             }
3981
3982             if (looking_at(buf, &i, "no longer examining game *")) {
3983                 if (gameMode == IcsExamining &&
3984                     atoi(star_match[0]) == ics_gamenum)
3985                   {
3986                       gameMode = IcsIdle;
3987                       ics_gamenum = -1;
3988                       ics_user_moved = FALSE;
3989                   }
3990                 continue;
3991             }
3992
3993             /* Advance leftover_start past any newlines we find,
3994                so only partial lines can get reparsed */
3995             if (looking_at(buf, &i, "\n")) {
3996                 prevColor = curColor;
3997                 if (curColor != ColorNormal) {
3998                     if (oldi > next_out) {
3999                         SendToPlayer(&buf[next_out], oldi - next_out);
4000                         next_out = oldi;
4001                     }
4002                     Colorize(ColorNormal, FALSE);
4003                     curColor = ColorNormal;
4004                 }
4005                 if (started == STARTED_BOARD) {
4006                     started = STARTED_NONE;
4007                     parse[parse_pos] = NULLCHAR;
4008                     ParseBoard12(parse);
4009                     ics_user_moved = 0;
4010
4011                     /* Send premove here */
4012                     if (appData.premove) {
4013                       char str[MSG_SIZ];
4014                       if (currentMove == 0 &&
4015                           gameMode == IcsPlayingWhite &&
4016                           appData.premoveWhite) {
4017                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4018                         if (appData.debugMode)
4019                           fprintf(debugFP, "Sending premove:\n");
4020                         SendToICS(str);
4021                       } else if (currentMove == 1 &&
4022                                  gameMode == IcsPlayingBlack &&
4023                                  appData.premoveBlack) {
4024                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4025                         if (appData.debugMode)
4026                           fprintf(debugFP, "Sending premove:\n");
4027                         SendToICS(str);
4028                       } else if (gotPremove) {
4029                         gotPremove = 0;
4030                         ClearPremoveHighlights();
4031                         if (appData.debugMode)
4032                           fprintf(debugFP, "Sending premove:\n");
4033                           UserMoveEvent(premoveFromX, premoveFromY,
4034                                         premoveToX, premoveToY,
4035                                         premovePromoChar);
4036                       }
4037                     }
4038
4039                     /* Usually suppress following prompt */
4040                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4041                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4042                         if (looking_at(buf, &i, "*% ")) {
4043                             savingComment = FALSE;
4044                             suppressKibitz = 0;
4045                         }
4046                     }
4047                     next_out = i;
4048                 } else if (started == STARTED_HOLDINGS) {
4049                     int gamenum;
4050                     char new_piece[MSG_SIZ];
4051                     started = STARTED_NONE;
4052                     parse[parse_pos] = NULLCHAR;
4053                     if (appData.debugMode)
4054                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4055                                                         parse, currentMove);
4056                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4057                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4058                         if (gameInfo.variant == VariantNormal) {
4059                           /* [HGM] We seem to switch variant during a game!
4060                            * Presumably no holdings were displayed, so we have
4061                            * to move the position two files to the right to
4062                            * create room for them!
4063                            */
4064                           VariantClass newVariant;
4065                           switch(gameInfo.boardWidth) { // base guess on board width
4066                                 case 9:  newVariant = VariantShogi; break;
4067                                 case 10: newVariant = VariantGreat; break;
4068                                 default: newVariant = VariantCrazyhouse; break;
4069                           }
4070                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4071                           /* Get a move list just to see the header, which
4072                              will tell us whether this is really bug or zh */
4073                           if (ics_getting_history == H_FALSE) {
4074                             ics_getting_history = H_REQUESTED;
4075                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4076                             SendToICS(str);
4077                           }
4078                         }
4079                         new_piece[0] = NULLCHAR;
4080                         sscanf(parse, "game %d white [%s black [%s <- %s",
4081                                &gamenum, white_holding, black_holding,
4082                                new_piece);
4083                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4084                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4085                         /* [HGM] copy holdings to board holdings area */
4086                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4087                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4088                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4089 #if ZIPPY
4090                         if (appData.zippyPlay && first.initDone) {
4091                             ZippyHoldings(white_holding, black_holding,
4092                                           new_piece);
4093                         }
4094 #endif /*ZIPPY*/
4095                         if (tinyLayout || smallLayout) {
4096                             char wh[16], bh[16];
4097                             PackHolding(wh, white_holding);
4098                             PackHolding(bh, black_holding);
4099                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4100                                     gameInfo.white, gameInfo.black);
4101                         } else {
4102                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4103                                     gameInfo.white, white_holding, _("vs."),
4104                                     gameInfo.black, black_holding);
4105                         }
4106                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4107                         DrawPosition(FALSE, boards[currentMove]);
4108                         DisplayTitle(str);
4109                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4110                         sscanf(parse, "game %d white [%s black [%s <- %s",
4111                                &gamenum, white_holding, black_holding,
4112                                new_piece);
4113                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4114                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4115                         /* [HGM] copy holdings to partner-board holdings area */
4116                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4117                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4118                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4119                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4120                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4121                       }
4122                     }
4123                     /* Suppress following prompt */
4124                     if (looking_at(buf, &i, "*% ")) {
4125                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4126                         savingComment = FALSE;
4127                         suppressKibitz = 0;
4128                     }
4129                     next_out = i;
4130                 }
4131                 continue;
4132             }
4133
4134             i++;                /* skip unparsed character and loop back */
4135         }
4136
4137         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4138 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4139 //          SendToPlayer(&buf[next_out], i - next_out);
4140             started != STARTED_HOLDINGS && leftover_start > next_out) {
4141             SendToPlayer(&buf[next_out], leftover_start - next_out);
4142             next_out = i;
4143         }
4144
4145         leftover_len = buf_len - leftover_start;
4146         /* if buffer ends with something we couldn't parse,
4147            reparse it after appending the next read */
4148
4149     } else if (count == 0) {
4150         RemoveInputSource(isr);
4151         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4152     } else {
4153         DisplayFatalError(_("Error reading from ICS"), error, 1);
4154     }
4155 }
4156
4157
4158 /* Board style 12 looks like this:
4159
4160    <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
4161
4162  * The "<12> " is stripped before it gets to this routine.  The two
4163  * trailing 0's (flip state and clock ticking) are later addition, and
4164  * some chess servers may not have them, or may have only the first.
4165  * Additional trailing fields may be added in the future.
4166  */
4167
4168 #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"
4169
4170 #define RELATION_OBSERVING_PLAYED    0
4171 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4172 #define RELATION_PLAYING_MYMOVE      1
4173 #define RELATION_PLAYING_NOTMYMOVE  -1
4174 #define RELATION_EXAMINING           2
4175 #define RELATION_ISOLATED_BOARD     -3
4176 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4177
4178 void
4179 ParseBoard12 (char *string)
4180 {
4181     GameMode newGameMode;
4182     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4183     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4184     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4185     char to_play, board_chars[200];
4186     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4187     char black[32], white[32];
4188     Board board;
4189     int prevMove = currentMove;
4190     int ticking = 2;
4191     ChessMove moveType;
4192     int fromX, fromY, toX, toY;
4193     char promoChar;
4194     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4195     char *bookHit = NULL; // [HGM] book
4196     Boolean weird = FALSE, reqFlag = FALSE;
4197
4198     fromX = fromY = toX = toY = -1;
4199
4200     newGame = FALSE;
4201
4202     if (appData.debugMode)
4203       fprintf(debugFP, _("Parsing board: %s\n"), string);
4204
4205     move_str[0] = NULLCHAR;
4206     elapsed_time[0] = NULLCHAR;
4207     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4208         int  i = 0, j;
4209         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4210             if(string[i] == ' ') { ranks++; files = 0; }
4211             else files++;
4212             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4213             i++;
4214         }
4215         for(j = 0; j <i; j++) board_chars[j] = string[j];
4216         board_chars[i] = '\0';
4217         string += i + 1;
4218     }
4219     n = sscanf(string, PATTERN, &to_play, &double_push,
4220                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4221                &gamenum, white, black, &relation, &basetime, &increment,
4222                &white_stren, &black_stren, &white_time, &black_time,
4223                &moveNum, str, elapsed_time, move_str, &ics_flip,
4224                &ticking);
4225
4226     if (n < 21) {
4227         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4228         DisplayError(str, 0);
4229         return;
4230     }
4231
4232     /* Convert the move number to internal form */
4233     moveNum = (moveNum - 1) * 2;
4234     if (to_play == 'B') moveNum++;
4235     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4236       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4237                         0, 1);
4238       return;
4239     }
4240
4241     switch (relation) {
4242       case RELATION_OBSERVING_PLAYED:
4243       case RELATION_OBSERVING_STATIC:
4244         if (gamenum == -1) {
4245             /* Old ICC buglet */
4246             relation = RELATION_OBSERVING_STATIC;
4247         }
4248         newGameMode = IcsObserving;
4249         break;
4250       case RELATION_PLAYING_MYMOVE:
4251       case RELATION_PLAYING_NOTMYMOVE:
4252         newGameMode =
4253           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4254             IcsPlayingWhite : IcsPlayingBlack;
4255         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4256         break;
4257       case RELATION_EXAMINING:
4258         newGameMode = IcsExamining;
4259         break;
4260       case RELATION_ISOLATED_BOARD:
4261       default:
4262         /* Just display this board.  If user was doing something else,
4263            we will forget about it until the next board comes. */
4264         newGameMode = IcsIdle;
4265         break;
4266       case RELATION_STARTING_POSITION:
4267         newGameMode = gameMode;
4268         break;
4269     }
4270
4271     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4272         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4273          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4274       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4275       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4276       static int lastBgGame = -1;
4277       char *toSqr;
4278       for (k = 0; k < ranks; k++) {
4279         for (j = 0; j < files; j++)
4280           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4281         if(gameInfo.holdingsWidth > 1) {
4282              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4283              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4284         }
4285       }
4286       CopyBoard(partnerBoard, board);
4287       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4288         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4289         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4290       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4291       if(toSqr = strchr(str, '-')) {
4292         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4293         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4294       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4295       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4296       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4297       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4298       if(twoBoards) {
4299           DisplayWhiteClock(white_time*fac, to_play == 'W');
4300           DisplayBlackClock(black_time*fac, to_play != 'W');
4301           activePartner = to_play;
4302           if(gamenum != lastBgGame) {
4303               char buf[MSG_SIZ];
4304               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4305               DisplayTitle(buf);
4306           }
4307           lastBgGame = gamenum;
4308           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4309                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4310       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4311                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4312       DisplayMessage(partnerStatus, "");
4313         partnerBoardValid = TRUE;
4314       return;
4315     }
4316
4317     if(appData.dualBoard && appData.bgObserve) {
4318         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4319             SendToICS(ics_prefix), SendToICS("pobserve\n");
4320         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4321             char buf[MSG_SIZ];
4322             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4323             SendToICS(buf);
4324         }
4325     }
4326
4327     /* Modify behavior for initial board display on move listing
4328        of wild games.
4329        */
4330     switch (ics_getting_history) {
4331       case H_FALSE:
4332       case H_REQUESTED:
4333         break;
4334       case H_GOT_REQ_HEADER:
4335       case H_GOT_UNREQ_HEADER:
4336         /* This is the initial position of the current game */
4337         gamenum = ics_gamenum;
4338         moveNum = 0;            /* old ICS bug workaround */
4339         if (to_play == 'B') {
4340           startedFromSetupPosition = TRUE;
4341           blackPlaysFirst = TRUE;
4342           moveNum = 1;
4343           if (forwardMostMove == 0) forwardMostMove = 1;
4344           if (backwardMostMove == 0) backwardMostMove = 1;
4345           if (currentMove == 0) currentMove = 1;
4346         }
4347         newGameMode = gameMode;
4348         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4349         break;
4350       case H_GOT_UNWANTED_HEADER:
4351         /* This is an initial board that we don't want */
4352         return;
4353       case H_GETTING_MOVES:
4354         /* Should not happen */
4355         DisplayError(_("Error gathering move list: extra board"), 0);
4356         ics_getting_history = H_FALSE;
4357         return;
4358     }
4359
4360    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4361                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4362                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4363      /* [HGM] We seem to have switched variant unexpectedly
4364       * Try to guess new variant from board size
4365       */
4366           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4367           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4368           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4369           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4370           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4371           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4372           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4373           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4374           /* Get a move list just to see the header, which
4375              will tell us whether this is really bug or zh */
4376           if (ics_getting_history == H_FALSE) {
4377             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4378             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4379             SendToICS(str);
4380           }
4381     }
4382
4383     /* Take action if this is the first board of a new game, or of a
4384        different game than is currently being displayed.  */
4385     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4386         relation == RELATION_ISOLATED_BOARD) {
4387
4388         /* Forget the old game and get the history (if any) of the new one */
4389         if (gameMode != BeginningOfGame) {
4390           Reset(TRUE, TRUE);
4391         }
4392         newGame = TRUE;
4393         if (appData.autoRaiseBoard) BoardToTop();
4394         prevMove = -3;
4395         if (gamenum == -1) {
4396             newGameMode = IcsIdle;
4397         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4398                    appData.getMoveList && !reqFlag) {
4399             /* Need to get game history */
4400             ics_getting_history = H_REQUESTED;
4401             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4402             SendToICS(str);
4403         }
4404
4405         /* Initially flip the board to have black on the bottom if playing
4406            black or if the ICS flip flag is set, but let the user change
4407            it with the Flip View button. */
4408         flipView = appData.autoFlipView ?
4409           (newGameMode == IcsPlayingBlack) || ics_flip :
4410           appData.flipView;
4411
4412         /* Done with values from previous mode; copy in new ones */
4413         gameMode = newGameMode;
4414         ModeHighlight();
4415         ics_gamenum = gamenum;
4416         if (gamenum == gs_gamenum) {
4417             int klen = strlen(gs_kind);
4418             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4419             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4420             gameInfo.event = StrSave(str);
4421         } else {
4422             gameInfo.event = StrSave("ICS game");
4423         }
4424         gameInfo.site = StrSave(appData.icsHost);
4425         gameInfo.date = PGNDate();
4426         gameInfo.round = StrSave("-");
4427         gameInfo.white = StrSave(white);
4428         gameInfo.black = StrSave(black);
4429         timeControl = basetime * 60 * 1000;
4430         timeControl_2 = 0;
4431         timeIncrement = increment * 1000;
4432         movesPerSession = 0;
4433         gameInfo.timeControl = TimeControlTagValue();
4434         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4435   if (appData.debugMode) {
4436     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4437     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4438     setbuf(debugFP, NULL);
4439   }
4440
4441         gameInfo.outOfBook = NULL;
4442
4443         /* Do we have the ratings? */
4444         if (strcmp(player1Name, white) == 0 &&
4445             strcmp(player2Name, black) == 0) {
4446             if (appData.debugMode)
4447               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4448                       player1Rating, player2Rating);
4449             gameInfo.whiteRating = player1Rating;
4450             gameInfo.blackRating = player2Rating;
4451         } else if (strcmp(player2Name, white) == 0 &&
4452                    strcmp(player1Name, black) == 0) {
4453             if (appData.debugMode)
4454               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4455                       player2Rating, player1Rating);
4456             gameInfo.whiteRating = player2Rating;
4457             gameInfo.blackRating = player1Rating;
4458         }
4459         player1Name[0] = player2Name[0] = NULLCHAR;
4460
4461         /* Silence shouts if requested */
4462         if (appData.quietPlay &&
4463             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4464             SendToICS(ics_prefix);
4465             SendToICS("set shout 0\n");
4466         }
4467     }
4468
4469     /* Deal with midgame name changes */
4470     if (!newGame) {
4471         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4472             if (gameInfo.white) free(gameInfo.white);
4473             gameInfo.white = StrSave(white);
4474         }
4475         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4476             if (gameInfo.black) free(gameInfo.black);
4477             gameInfo.black = StrSave(black);
4478         }
4479     }
4480
4481     /* Throw away game result if anything actually changes in examine mode */
4482     if (gameMode == IcsExamining && !newGame) {
4483         gameInfo.result = GameUnfinished;
4484         if (gameInfo.resultDetails != NULL) {
4485             free(gameInfo.resultDetails);
4486             gameInfo.resultDetails = NULL;
4487         }
4488     }
4489
4490     /* In pausing && IcsExamining mode, we ignore boards coming
4491        in if they are in a different variation than we are. */
4492     if (pauseExamInvalid) return;
4493     if (pausing && gameMode == IcsExamining) {
4494         if (moveNum <= pauseExamForwardMostMove) {
4495             pauseExamInvalid = TRUE;
4496             forwardMostMove = pauseExamForwardMostMove;
4497             return;
4498         }
4499     }
4500
4501   if (appData.debugMode) {
4502     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4503   }
4504     /* Parse the board */
4505     for (k = 0; k < ranks; k++) {
4506       for (j = 0; j < files; j++)
4507         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4508       if(gameInfo.holdingsWidth > 1) {
4509            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4510            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4511       }
4512     }
4513     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4514       board[5][BOARD_RGHT+1] = WhiteAngel;
4515       board[6][BOARD_RGHT+1] = WhiteMarshall;
4516       board[1][0] = BlackMarshall;
4517       board[2][0] = BlackAngel;
4518       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4519     }
4520     CopyBoard(boards[moveNum], board);
4521     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4522     if (moveNum == 0) {
4523         startedFromSetupPosition =
4524           !CompareBoards(board, initialPosition);
4525         if(startedFromSetupPosition)
4526             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4527     }
4528
4529     /* [HGM] Set castling rights. Take the outermost Rooks,
4530        to make it also work for FRC opening positions. Note that board12
4531        is really defective for later FRC positions, as it has no way to
4532        indicate which Rook can castle if they are on the same side of King.
4533        For the initial position we grant rights to the outermost Rooks,
4534        and remember thos rights, and we then copy them on positions
4535        later in an FRC game. This means WB might not recognize castlings with
4536        Rooks that have moved back to their original position as illegal,
4537        but in ICS mode that is not its job anyway.
4538     */
4539     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4540     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4541
4542         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4543             if(board[0][i] == WhiteRook) j = i;
4544         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4545         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4546             if(board[0][i] == WhiteRook) j = i;
4547         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4548         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4549             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4550         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4551         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4552             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4553         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4554
4555         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4556         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4557         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4558             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4559         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4560             if(board[BOARD_HEIGHT-1][k] == bKing)
4561                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4562         if(gameInfo.variant == VariantTwoKings) {
4563             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4564             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4565             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4566         }
4567     } else { int r;
4568         r = boards[moveNum][CASTLING][0] = initialRights[0];
4569         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4570         r = boards[moveNum][CASTLING][1] = initialRights[1];
4571         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4572         r = boards[moveNum][CASTLING][3] = initialRights[3];
4573         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4574         r = boards[moveNum][CASTLING][4] = initialRights[4];
4575         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4576         /* wildcastle kludge: always assume King has rights */
4577         r = boards[moveNum][CASTLING][2] = initialRights[2];
4578         r = boards[moveNum][CASTLING][5] = initialRights[5];
4579     }
4580     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4581     boards[moveNum][EP_STATUS] = EP_NONE;
4582     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4583     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4584     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4585
4586
4587     if (ics_getting_history == H_GOT_REQ_HEADER ||
4588         ics_getting_history == H_GOT_UNREQ_HEADER) {
4589         /* This was an initial position from a move list, not
4590            the current position */
4591         return;
4592     }
4593
4594     /* Update currentMove and known move number limits */
4595     newMove = newGame || moveNum > forwardMostMove;
4596
4597     if (newGame) {
4598         forwardMostMove = backwardMostMove = currentMove = moveNum;
4599         if (gameMode == IcsExamining && moveNum == 0) {
4600           /* Workaround for ICS limitation: we are not told the wild
4601              type when starting to examine a game.  But if we ask for
4602              the move list, the move list header will tell us */
4603             ics_getting_history = H_REQUESTED;
4604             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4605             SendToICS(str);
4606         }
4607     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4608                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4609 #if ZIPPY
4610         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4611         /* [HGM] applied this also to an engine that is silently watching        */
4612         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4613             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4614             gameInfo.variant == currentlyInitializedVariant) {
4615           takeback = forwardMostMove - moveNum;
4616           for (i = 0; i < takeback; i++) {
4617             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4618             SendToProgram("undo\n", &first);
4619           }
4620         }
4621 #endif
4622
4623         forwardMostMove = moveNum;
4624         if (!pausing || currentMove > forwardMostMove)
4625           currentMove = forwardMostMove;
4626     } else {
4627         /* New part of history that is not contiguous with old part */
4628         if (pausing && gameMode == IcsExamining) {
4629             pauseExamInvalid = TRUE;
4630             forwardMostMove = pauseExamForwardMostMove;
4631             return;
4632         }
4633         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4634 #if ZIPPY
4635             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4636                 // [HGM] when we will receive the move list we now request, it will be
4637                 // fed to the engine from the first move on. So if the engine is not
4638                 // in the initial position now, bring it there.
4639                 InitChessProgram(&first, 0);
4640             }
4641 #endif
4642             ics_getting_history = H_REQUESTED;
4643             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4644             SendToICS(str);
4645         }
4646         forwardMostMove = backwardMostMove = currentMove = moveNum;
4647     }
4648
4649     /* Update the clocks */
4650     if (strchr(elapsed_time, '.')) {
4651       /* Time is in ms */
4652       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4653       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4654     } else {
4655       /* Time is in seconds */
4656       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4657       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4658     }
4659
4660
4661 #if ZIPPY
4662     if (appData.zippyPlay && newGame &&
4663         gameMode != IcsObserving && gameMode != IcsIdle &&
4664         gameMode != IcsExamining)
4665       ZippyFirstBoard(moveNum, basetime, increment);
4666 #endif
4667
4668     /* Put the move on the move list, first converting
4669        to canonical algebraic form. */
4670     if (moveNum > 0) {
4671   if (appData.debugMode) {
4672     if (appData.debugMode) { int f = forwardMostMove;
4673         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4674                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4675                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4676     }
4677     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4678     fprintf(debugFP, "moveNum = %d\n", moveNum);
4679     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4680     setbuf(debugFP, NULL);
4681   }
4682         if (moveNum <= backwardMostMove) {
4683             /* We don't know what the board looked like before
4684                this move.  Punt. */
4685           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4686             strcat(parseList[moveNum - 1], " ");
4687             strcat(parseList[moveNum - 1], elapsed_time);
4688             moveList[moveNum - 1][0] = NULLCHAR;
4689         } else if (strcmp(move_str, "none") == 0) {
4690             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4691             /* Again, we don't know what the board looked like;
4692                this is really the start of the game. */
4693             parseList[moveNum - 1][0] = NULLCHAR;
4694             moveList[moveNum - 1][0] = NULLCHAR;
4695             backwardMostMove = moveNum;
4696             startedFromSetupPosition = TRUE;
4697             fromX = fromY = toX = toY = -1;
4698         } else {
4699           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4700           //                 So we parse the long-algebraic move string in stead of the SAN move
4701           int valid; char buf[MSG_SIZ], *prom;
4702
4703           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4704                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4705           // str looks something like "Q/a1-a2"; kill the slash
4706           if(str[1] == '/')
4707             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4708           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4709           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4710                 strcat(buf, prom); // long move lacks promo specification!
4711           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4712                 if(appData.debugMode)
4713                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4714                 safeStrCpy(move_str, buf, MSG_SIZ);
4715           }
4716           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4717                                 &fromX, &fromY, &toX, &toY, &promoChar)
4718                || ParseOneMove(buf, moveNum - 1, &moveType,
4719                                 &fromX, &fromY, &toX, &toY, &promoChar);
4720           // end of long SAN patch
4721           if (valid) {
4722             (void) CoordsToAlgebraic(boards[moveNum - 1],
4723                                      PosFlags(moveNum - 1),
4724                                      fromY, fromX, toY, toX, promoChar,
4725                                      parseList[moveNum-1]);
4726             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4727               case MT_NONE:
4728               case MT_STALEMATE:
4729               default:
4730                 break;
4731               case MT_CHECK:
4732                 if(gameInfo.variant != VariantShogi)
4733                     strcat(parseList[moveNum - 1], "+");
4734                 break;
4735               case MT_CHECKMATE:
4736               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4737                 strcat(parseList[moveNum - 1], "#");
4738                 break;
4739             }
4740             strcat(parseList[moveNum - 1], " ");
4741             strcat(parseList[moveNum - 1], elapsed_time);
4742             /* currentMoveString is set as a side-effect of ParseOneMove */
4743             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4744             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4745             strcat(moveList[moveNum - 1], "\n");
4746
4747             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4748                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4749               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4750                 ChessSquare old, new = boards[moveNum][k][j];
4751                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4752                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4753                   if(old == new) continue;
4754                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4755                   else if(new == WhiteWazir || new == BlackWazir) {
4756                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4757                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4758                       else boards[moveNum][k][j] = old; // preserve type of Gold
4759                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4760                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4761               }
4762           } else {
4763             /* Move from ICS was illegal!?  Punt. */
4764             if (appData.debugMode) {
4765               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4766               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4767             }
4768             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4769             strcat(parseList[moveNum - 1], " ");
4770             strcat(parseList[moveNum - 1], elapsed_time);
4771             moveList[moveNum - 1][0] = NULLCHAR;
4772             fromX = fromY = toX = toY = -1;
4773           }
4774         }
4775   if (appData.debugMode) {
4776     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4777     setbuf(debugFP, NULL);
4778   }
4779
4780 #if ZIPPY
4781         /* Send move to chess program (BEFORE animating it). */
4782         if (appData.zippyPlay && !newGame && newMove &&
4783            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4784
4785             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4786                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4787                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4788                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4789                             move_str);
4790                     DisplayError(str, 0);
4791                 } else {
4792                     if (first.sendTime) {
4793                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4794                     }
4795                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4796                     if (firstMove && !bookHit) {
4797                         firstMove = FALSE;
4798                         if (first.useColors) {
4799                           SendToProgram(gameMode == IcsPlayingWhite ?
4800                                         "white\ngo\n" :
4801                                         "black\ngo\n", &first);
4802                         } else {
4803                           SendToProgram("go\n", &first);
4804                         }
4805                         first.maybeThinking = TRUE;
4806                     }
4807                 }
4808             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4809               if (moveList[moveNum - 1][0] == NULLCHAR) {
4810                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4811                 DisplayError(str, 0);
4812               } else {
4813                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4814                 SendMoveToProgram(moveNum - 1, &first);
4815               }
4816             }
4817         }
4818 #endif
4819     }
4820
4821     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4822         /* If move comes from a remote source, animate it.  If it
4823            isn't remote, it will have already been animated. */
4824         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4825             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4826         }
4827         if (!pausing && appData.highlightLastMove) {
4828             SetHighlights(fromX, fromY, toX, toY);
4829         }
4830     }
4831
4832     /* Start the clocks */
4833     whiteFlag = blackFlag = FALSE;
4834     appData.clockMode = !(basetime == 0 && increment == 0);
4835     if (ticking == 0) {
4836       ics_clock_paused = TRUE;
4837       StopClocks();
4838     } else if (ticking == 1) {
4839       ics_clock_paused = FALSE;
4840     }
4841     if (gameMode == IcsIdle ||
4842         relation == RELATION_OBSERVING_STATIC ||
4843         relation == RELATION_EXAMINING ||
4844         ics_clock_paused)
4845       DisplayBothClocks();
4846     else
4847       StartClocks();
4848
4849     /* Display opponents and material strengths */
4850     if (gameInfo.variant != VariantBughouse &&
4851         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4852         if (tinyLayout || smallLayout) {
4853             if(gameInfo.variant == VariantNormal)
4854               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4855                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4856                     basetime, increment);
4857             else
4858               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4859                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4860                     basetime, increment, (int) gameInfo.variant);
4861         } else {
4862             if(gameInfo.variant == VariantNormal)
4863               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4864                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4865                     basetime, increment);
4866             else
4867               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4868                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4869                     basetime, increment, VariantName(gameInfo.variant));
4870         }
4871         DisplayTitle(str);
4872   if (appData.debugMode) {
4873     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4874   }
4875     }
4876
4877
4878     /* Display the board */
4879     if (!pausing && !appData.noGUI) {
4880
4881       if (appData.premove)
4882           if (!gotPremove ||
4883              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4884              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4885               ClearPremoveHighlights();
4886
4887       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4888         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4889       DrawPosition(j, boards[currentMove]);
4890
4891       DisplayMove(moveNum - 1);
4892       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4893             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4894               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4895         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4896       }
4897     }
4898
4899     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4900 #if ZIPPY
4901     if(bookHit) { // [HGM] book: simulate book reply
4902         static char bookMove[MSG_SIZ]; // a bit generous?
4903
4904         programStats.nodes = programStats.depth = programStats.time =
4905         programStats.score = programStats.got_only_move = 0;
4906         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4907
4908         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4909         strcat(bookMove, bookHit);
4910         HandleMachineMove(bookMove, &first);
4911     }
4912 #endif
4913 }
4914
4915 void
4916 GetMoveListEvent ()
4917 {
4918     char buf[MSG_SIZ];
4919     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4920         ics_getting_history = H_REQUESTED;
4921         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4922         SendToICS(buf);
4923     }
4924 }
4925
4926 void
4927 SendToBoth (char *msg)
4928 {   // to make it easy to keep two engines in step in dual analysis
4929     SendToProgram(msg, &first);
4930     if(second.analyzing) SendToProgram(msg, &second);
4931 }
4932
4933 void
4934 AnalysisPeriodicEvent (int force)
4935 {
4936     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4937          && !force) || !appData.periodicUpdates)
4938       return;
4939
4940     /* Send . command to Crafty to collect stats */
4941     SendToBoth(".\n");
4942
4943     /* Don't send another until we get a response (this makes
4944        us stop sending to old Crafty's which don't understand
4945        the "." command (sending illegal cmds resets node count & time,
4946        which looks bad)) */
4947     programStats.ok_to_send = 0;
4948 }
4949
4950 void
4951 ics_update_width (int new_width)
4952 {
4953         ics_printf("set width %d\n", new_width);
4954 }
4955
4956 void
4957 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4958 {
4959     char buf[MSG_SIZ];
4960
4961     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4962         // null move in variant where engine does not understand it (for analysis purposes)
4963         SendBoard(cps, moveNum + 1); // send position after move in stead.
4964         return;
4965     }
4966     if (cps->useUsermove) {
4967       SendToProgram("usermove ", cps);
4968     }
4969     if (cps->useSAN) {
4970       char *space;
4971       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4972         int len = space - parseList[moveNum];
4973         memcpy(buf, parseList[moveNum], len);
4974         buf[len++] = '\n';
4975         buf[len] = NULLCHAR;
4976       } else {
4977         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4978       }
4979       SendToProgram(buf, cps);
4980     } else {
4981       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4982         AlphaRank(moveList[moveNum], 4);
4983         SendToProgram(moveList[moveNum], cps);
4984         AlphaRank(moveList[moveNum], 4); // and back
4985       } else
4986       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4987        * the engine. It would be nice to have a better way to identify castle
4988        * moves here. */
4989       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4990                                                                          && cps->useOOCastle) {
4991         int fromX = moveList[moveNum][0] - AAA;
4992         int fromY = moveList[moveNum][1] - ONE;
4993         int toX = moveList[moveNum][2] - AAA;
4994         int toY = moveList[moveNum][3] - ONE;
4995         if((boards[moveNum][fromY][fromX] == WhiteKing
4996             && boards[moveNum][toY][toX] == WhiteRook)
4997            || (boards[moveNum][fromY][fromX] == BlackKing
4998                && boards[moveNum][toY][toX] == BlackRook)) {
4999           if(toX > fromX) SendToProgram("O-O\n", cps);
5000           else SendToProgram("O-O-O\n", cps);
5001         }
5002         else SendToProgram(moveList[moveNum], cps);
5003       } else
5004       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5005         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5006           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5007           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5008                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5009         } else
5010           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5011                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5012         SendToProgram(buf, cps);
5013       }
5014       else SendToProgram(moveList[moveNum], cps);
5015       /* End of additions by Tord */
5016     }
5017
5018     /* [HGM] setting up the opening has brought engine in force mode! */
5019     /*       Send 'go' if we are in a mode where machine should play. */
5020     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5021         (gameMode == TwoMachinesPlay   ||
5022 #if ZIPPY
5023          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5024 #endif
5025          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5026         SendToProgram("go\n", cps);
5027   if (appData.debugMode) {
5028     fprintf(debugFP, "(extra)\n");
5029   }
5030     }
5031     setboardSpoiledMachineBlack = 0;
5032 }
5033
5034 void
5035 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5036 {
5037     char user_move[MSG_SIZ];
5038     char suffix[4];
5039
5040     if(gameInfo.variant == VariantSChess && promoChar) {
5041         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5042         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5043     } else suffix[0] = NULLCHAR;
5044
5045     switch (moveType) {
5046       default:
5047         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5048                 (int)moveType, fromX, fromY, toX, toY);
5049         DisplayError(user_move + strlen("say "), 0);
5050         break;
5051       case WhiteKingSideCastle:
5052       case BlackKingSideCastle:
5053       case WhiteQueenSideCastleWild:
5054       case BlackQueenSideCastleWild:
5055       /* PUSH Fabien */
5056       case WhiteHSideCastleFR:
5057       case BlackHSideCastleFR:
5058       /* POP Fabien */
5059         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5060         break;
5061       case WhiteQueenSideCastle:
5062       case BlackQueenSideCastle:
5063       case WhiteKingSideCastleWild:
5064       case BlackKingSideCastleWild:
5065       /* PUSH Fabien */
5066       case WhiteASideCastleFR:
5067       case BlackASideCastleFR:
5068       /* POP Fabien */
5069         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5070         break;
5071       case WhiteNonPromotion:
5072       case BlackNonPromotion:
5073         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5074         break;
5075       case WhitePromotion:
5076       case BlackPromotion:
5077         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5078           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5079                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5080                 PieceToChar(WhiteFerz));
5081         else if(gameInfo.variant == VariantGreat)
5082           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5084                 PieceToChar(WhiteMan));
5085         else
5086           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5087                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5088                 promoChar);
5089         break;
5090       case WhiteDrop:
5091       case BlackDrop:
5092       drop:
5093         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5094                  ToUpper(PieceToChar((ChessSquare) fromX)),
5095                  AAA + toX, ONE + toY);
5096         break;
5097       case IllegalMove:  /* could be a variant we don't quite understand */
5098         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5099       case NormalMove:
5100       case WhiteCapturesEnPassant:
5101       case BlackCapturesEnPassant:
5102         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5103                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5104         break;
5105     }
5106     SendToICS(user_move);
5107     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5108         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5109 }
5110
5111 void
5112 UploadGameEvent ()
5113 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5114     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5115     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5116     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5117       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5118       return;
5119     }
5120     if(gameMode != IcsExamining) { // is this ever not the case?
5121         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5122
5123         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5124           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5125         } else { // on FICS we must first go to general examine mode
5126           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5127         }
5128         if(gameInfo.variant != VariantNormal) {
5129             // try figure out wild number, as xboard names are not always valid on ICS
5130             for(i=1; i<=36; i++) {
5131               snprintf(buf, MSG_SIZ, "wild/%d", i);
5132                 if(StringToVariant(buf) == gameInfo.variant) break;
5133             }
5134             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5135             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5136             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5137         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5138         SendToICS(ics_prefix);
5139         SendToICS(buf);
5140         if(startedFromSetupPosition || backwardMostMove != 0) {
5141           fen = PositionToFEN(backwardMostMove, NULL);
5142           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5143             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5144             SendToICS(buf);
5145           } else { // FICS: everything has to set by separate bsetup commands
5146             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5147             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5148             SendToICS(buf);
5149             if(!WhiteOnMove(backwardMostMove)) {
5150                 SendToICS("bsetup tomove black\n");
5151             }
5152             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5153             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5154             SendToICS(buf);
5155             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5156             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5157             SendToICS(buf);
5158             i = boards[backwardMostMove][EP_STATUS];
5159             if(i >= 0) { // set e.p.
5160               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5161                 SendToICS(buf);
5162             }
5163             bsetup++;
5164           }
5165         }
5166       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5167             SendToICS("bsetup done\n"); // switch to normal examining.
5168     }
5169     for(i = backwardMostMove; i<last; i++) {
5170         char buf[20];
5171         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5172         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5173             int len = strlen(moveList[i]);
5174             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5175             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5176         }
5177         SendToICS(buf);
5178     }
5179     SendToICS(ics_prefix);
5180     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5181 }
5182
5183 void
5184 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5185 {
5186     if (rf == DROP_RANK) {
5187       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5188       sprintf(move, "%c@%c%c\n",
5189                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5190     } else {
5191         if (promoChar == 'x' || promoChar == NULLCHAR) {
5192           sprintf(move, "%c%c%c%c\n",
5193                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5194         } else {
5195             sprintf(move, "%c%c%c%c%c\n",
5196                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5197         }
5198     }
5199 }
5200
5201 void
5202 ProcessICSInitScript (FILE *f)
5203 {
5204     char buf[MSG_SIZ];
5205
5206     while (fgets(buf, MSG_SIZ, f)) {
5207         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5208     }
5209
5210     fclose(f);
5211 }
5212
5213
5214 static int lastX, lastY, selectFlag, dragging;
5215
5216 void
5217 Sweep (int step)
5218 {
5219     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5220     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5221     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5222     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5223     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5224     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5225     do {
5226         promoSweep -= step;
5227         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5228         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5229         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5230         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5231         if(!step) step = -1;
5232     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5233             appData.testLegality && (promoSweep == king ||
5234             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5235     if(toX >= 0) {
5236         int victim = boards[currentMove][toY][toX];
5237         boards[currentMove][toY][toX] = promoSweep;
5238         DrawPosition(FALSE, boards[currentMove]);
5239         boards[currentMove][toY][toX] = victim;
5240     } else
5241     ChangeDragPiece(promoSweep);
5242 }
5243
5244 int
5245 PromoScroll (int x, int y)
5246 {
5247   int step = 0;
5248
5249   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5250   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5251   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5252   if(!step) return FALSE;
5253   lastX = x; lastY = y;
5254   if((promoSweep < BlackPawn) == flipView) step = -step;
5255   if(step > 0) selectFlag = 1;
5256   if(!selectFlag) Sweep(step);
5257   return FALSE;
5258 }
5259
5260 void
5261 NextPiece (int step)
5262 {
5263     ChessSquare piece = boards[currentMove][toY][toX];
5264     do {
5265         pieceSweep -= step;
5266         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5267         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5268         if(!step) step = -1;
5269     } while(PieceToChar(pieceSweep) == '.');
5270     boards[currentMove][toY][toX] = pieceSweep;
5271     DrawPosition(FALSE, boards[currentMove]);
5272     boards[currentMove][toY][toX] = piece;
5273 }
5274 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5275 void
5276 AlphaRank (char *move, int n)
5277 {
5278 //    char *p = move, c; int x, y;
5279
5280     if (appData.debugMode) {
5281         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5282     }
5283
5284     if(move[1]=='*' &&
5285        move[2]>='0' && move[2]<='9' &&
5286        move[3]>='a' && move[3]<='x'    ) {
5287         move[1] = '@';
5288         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5289         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5290     } else
5291     if(move[0]>='0' && move[0]<='9' &&
5292        move[1]>='a' && move[1]<='x' &&
5293        move[2]>='0' && move[2]<='9' &&
5294        move[3]>='a' && move[3]<='x'    ) {
5295         /* input move, Shogi -> normal */
5296         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5297         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5298         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5299         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5300     } else
5301     if(move[1]=='@' &&
5302        move[3]>='0' && move[3]<='9' &&
5303        move[2]>='a' && move[2]<='x'    ) {
5304         move[1] = '*';
5305         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5306         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5307     } else
5308     if(
5309        move[0]>='a' && move[0]<='x' &&
5310        move[3]>='0' && move[3]<='9' &&
5311        move[2]>='a' && move[2]<='x'    ) {
5312          /* output move, normal -> Shogi */
5313         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5314         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5315         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5316         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5317         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5318     }
5319     if (appData.debugMode) {
5320         fprintf(debugFP, "   out = '%s'\n", move);
5321     }
5322 }
5323
5324 char yy_textstr[8000];
5325
5326 /* Parser for moves from gnuchess, ICS, or user typein box */
5327 Boolean
5328 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5329 {
5330     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5331
5332     switch (*moveType) {
5333       case WhitePromotion:
5334       case BlackPromotion:
5335       case WhiteNonPromotion:
5336       case BlackNonPromotion:
5337       case NormalMove:
5338       case WhiteCapturesEnPassant:
5339       case BlackCapturesEnPassant:
5340       case WhiteKingSideCastle:
5341       case WhiteQueenSideCastle:
5342       case BlackKingSideCastle:
5343       case BlackQueenSideCastle:
5344       case WhiteKingSideCastleWild:
5345       case WhiteQueenSideCastleWild:
5346       case BlackKingSideCastleWild:
5347       case BlackQueenSideCastleWild:
5348       /* Code added by Tord: */
5349       case WhiteHSideCastleFR:
5350       case WhiteASideCastleFR:
5351       case BlackHSideCastleFR:
5352       case BlackASideCastleFR:
5353       /* End of code added by Tord */
5354       case IllegalMove:         /* bug or odd chess variant */
5355         *fromX = currentMoveString[0] - AAA;
5356         *fromY = currentMoveString[1] - ONE;
5357         *toX = currentMoveString[2] - AAA;
5358         *toY = currentMoveString[3] - ONE;
5359         *promoChar = currentMoveString[4];
5360         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5361             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5362     if (appData.debugMode) {
5363         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5364     }
5365             *fromX = *fromY = *toX = *toY = 0;
5366             return FALSE;
5367         }
5368         if (appData.testLegality) {
5369           return (*moveType != IllegalMove);
5370         } else {
5371           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5372                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5373         }
5374
5375       case WhiteDrop:
5376       case BlackDrop:
5377         *fromX = *moveType == WhiteDrop ?
5378           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5379           (int) CharToPiece(ToLower(currentMoveString[0]));
5380         *fromY = DROP_RANK;
5381         *toX = currentMoveString[2] - AAA;
5382         *toY = currentMoveString[3] - ONE;
5383         *promoChar = NULLCHAR;
5384         return TRUE;
5385
5386       case AmbiguousMove:
5387       case ImpossibleMove:
5388       case EndOfFile:
5389       case ElapsedTime:
5390       case Comment:
5391       case PGNTag:
5392       case NAG:
5393       case WhiteWins:
5394       case BlackWins:
5395       case GameIsDrawn:
5396       default:
5397     if (appData.debugMode) {
5398         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5399     }
5400         /* bug? */
5401         *fromX = *fromY = *toX = *toY = 0;
5402         *promoChar = NULLCHAR;
5403         return FALSE;
5404     }
5405 }
5406
5407 Boolean pushed = FALSE;
5408 char *lastParseAttempt;
5409
5410 void
5411 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5412 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5413   int fromX, fromY, toX, toY; char promoChar;
5414   ChessMove moveType;
5415   Boolean valid;
5416   int nr = 0;
5417
5418   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5419     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5420     pushed = TRUE;
5421   }
5422   endPV = forwardMostMove;
5423   do {
5424     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5425     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5426     lastParseAttempt = pv;
5427     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5428     if(!valid && nr == 0 &&
5429        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5430         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5431         // Hande case where played move is different from leading PV move
5432         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5433         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5434         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5435         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5436           endPV += 2; // if position different, keep this
5437           moveList[endPV-1][0] = fromX + AAA;
5438           moveList[endPV-1][1] = fromY + ONE;
5439           moveList[endPV-1][2] = toX + AAA;
5440           moveList[endPV-1][3] = toY + ONE;
5441           parseList[endPV-1][0] = NULLCHAR;
5442           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5443         }
5444       }
5445     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5446     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5447     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5448     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5449         valid++; // allow comments in PV
5450         continue;
5451     }
5452     nr++;
5453     if(endPV+1 > framePtr) break; // no space, truncate
5454     if(!valid) break;
5455     endPV++;
5456     CopyBoard(boards[endPV], boards[endPV-1]);
5457     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5458     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5459     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5460     CoordsToAlgebraic(boards[endPV - 1],
5461                              PosFlags(endPV - 1),
5462                              fromY, fromX, toY, toX, promoChar,
5463                              parseList[endPV - 1]);
5464   } while(valid);
5465   if(atEnd == 2) return; // used hidden, for PV conversion
5466   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5467   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5468   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5469                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5470   DrawPosition(TRUE, boards[currentMove]);
5471 }
5472
5473 int
5474 MultiPV (ChessProgramState *cps)
5475 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5476         int i;
5477         for(i=0; i<cps->nrOptions; i++)
5478             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5479                 return i;
5480         return -1;
5481 }
5482
5483 Boolean
5484 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5485 {
5486         int startPV, multi, lineStart, origIndex = index;
5487         char *p, buf2[MSG_SIZ];
5488         ChessProgramState *cps = (pane ? &second : &first);
5489
5490         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5491         lastX = x; lastY = y;
5492         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5493         lineStart = startPV = index;
5494         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5495         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5496         index = startPV;
5497         do{ while(buf[index] && buf[index] != '\n') index++;
5498         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5499         buf[index] = 0;
5500         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5501                 int n = cps->option[multi].value;
5502                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5503                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5504                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5505                 cps->option[multi].value = n;
5506                 *start = *end = 0;
5507                 return FALSE;
5508         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5509                 ExcludeClick(origIndex - lineStart);
5510                 return FALSE;
5511         }
5512         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5513         *start = startPV; *end = index-1;
5514         return TRUE;
5515 }
5516
5517 char *
5518 PvToSAN (char *pv)
5519 {
5520         static char buf[10*MSG_SIZ];
5521         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5522         *buf = NULLCHAR;
5523         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5524         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5525         for(i = forwardMostMove; i<endPV; i++){
5526             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5527             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5528             k += strlen(buf+k);
5529         }
5530         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5531         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5532         endPV = savedEnd;
5533         return buf;
5534 }
5535
5536 Boolean
5537 LoadPV (int x, int y)
5538 { // called on right mouse click to load PV
5539   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5540   lastX = x; lastY = y;
5541   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5542   return TRUE;
5543 }
5544
5545 void
5546 UnLoadPV ()
5547 {
5548   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5549   if(endPV < 0) return;
5550   if(appData.autoCopyPV) CopyFENToClipboard();
5551   endPV = -1;
5552   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5553         Boolean saveAnimate = appData.animate;
5554         if(pushed) {
5555             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5556                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5557             } else storedGames--; // abandon shelved tail of original game
5558         }
5559         pushed = FALSE;
5560         forwardMostMove = currentMove;
5561         currentMove = oldFMM;
5562         appData.animate = FALSE;
5563         ToNrEvent(forwardMostMove);
5564         appData.animate = saveAnimate;
5565   }
5566   currentMove = forwardMostMove;
5567   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5568   ClearPremoveHighlights();
5569   DrawPosition(TRUE, boards[currentMove]);
5570 }
5571
5572 void
5573 MovePV (int x, int y, int h)
5574 { // step through PV based on mouse coordinates (called on mouse move)
5575   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5576
5577   // we must somehow check if right button is still down (might be released off board!)
5578   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5579   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5580   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5581   if(!step) return;
5582   lastX = x; lastY = y;
5583
5584   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5585   if(endPV < 0) return;
5586   if(y < margin) step = 1; else
5587   if(y > h - margin) step = -1;
5588   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5589   currentMove += step;
5590   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5591   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5592                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5593   DrawPosition(FALSE, boards[currentMove]);
5594 }
5595
5596
5597 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5598 // All positions will have equal probability, but the current method will not provide a unique
5599 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5600 #define DARK 1
5601 #define LITE 2
5602 #define ANY 3
5603
5604 int squaresLeft[4];
5605 int piecesLeft[(int)BlackPawn];
5606 int seed, nrOfShuffles;
5607
5608 void
5609 GetPositionNumber ()
5610 {       // sets global variable seed
5611         int i;
5612
5613         seed = appData.defaultFrcPosition;
5614         if(seed < 0) { // randomize based on time for negative FRC position numbers
5615                 for(i=0; i<50; i++) seed += random();
5616                 seed = random() ^ random() >> 8 ^ random() << 8;
5617                 if(seed<0) seed = -seed;
5618         }
5619 }
5620
5621 int
5622 put (Board board, int pieceType, int rank, int n, int shade)
5623 // put the piece on the (n-1)-th empty squares of the given shade
5624 {
5625         int i;
5626
5627         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5628                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5629                         board[rank][i] = (ChessSquare) pieceType;
5630                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5631                         squaresLeft[ANY]--;
5632                         piecesLeft[pieceType]--;
5633                         return i;
5634                 }
5635         }
5636         return -1;
5637 }
5638
5639
5640 void
5641 AddOnePiece (Board board, int pieceType, int rank, int shade)
5642 // calculate where the next piece goes, (any empty square), and put it there
5643 {
5644         int i;
5645
5646         i = seed % squaresLeft[shade];
5647         nrOfShuffles *= squaresLeft[shade];
5648         seed /= squaresLeft[shade];
5649         put(board, pieceType, rank, i, shade);
5650 }
5651
5652 void
5653 AddTwoPieces (Board board, int pieceType, int rank)
5654 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5655 {
5656         int i, n=squaresLeft[ANY], j=n-1, k;
5657
5658         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5659         i = seed % k;  // pick one
5660         nrOfShuffles *= k;
5661         seed /= k;
5662         while(i >= j) i -= j--;
5663         j = n - 1 - j; i += j;
5664         put(board, pieceType, rank, j, ANY);
5665         put(board, pieceType, rank, i, ANY);
5666 }
5667
5668 void
5669 SetUpShuffle (Board board, int number)
5670 {
5671         int i, p, first=1;
5672
5673         GetPositionNumber(); nrOfShuffles = 1;
5674
5675         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5676         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5677         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5678
5679         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5680
5681         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5682             p = (int) board[0][i];
5683             if(p < (int) BlackPawn) piecesLeft[p] ++;
5684             board[0][i] = EmptySquare;
5685         }
5686
5687         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5688             // shuffles restricted to allow normal castling put KRR first
5689             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5690                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5691             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5692                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5693             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5694                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5695             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5696                 put(board, WhiteRook, 0, 0, ANY);
5697             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5698         }
5699
5700         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5701             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5702             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5703                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5704                 while(piecesLeft[p] >= 2) {
5705                     AddOnePiece(board, p, 0, LITE);
5706                     AddOnePiece(board, p, 0, DARK);
5707                 }
5708                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5709             }
5710
5711         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5712             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5713             // but we leave King and Rooks for last, to possibly obey FRC restriction
5714             if(p == (int)WhiteRook) continue;
5715             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5716             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5717         }
5718
5719         // now everything is placed, except perhaps King (Unicorn) and Rooks
5720
5721         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5722             // Last King gets castling rights
5723             while(piecesLeft[(int)WhiteUnicorn]) {
5724                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5725                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5726             }
5727
5728             while(piecesLeft[(int)WhiteKing]) {
5729                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5730                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5731             }
5732
5733
5734         } else {
5735             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5736             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5737         }
5738
5739         // Only Rooks can be left; simply place them all
5740         while(piecesLeft[(int)WhiteRook]) {
5741                 i = put(board, WhiteRook, 0, 0, ANY);
5742                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5743                         if(first) {
5744                                 first=0;
5745                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5746                         }
5747                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5748                 }
5749         }
5750         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5751             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5752         }
5753
5754         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5755 }
5756
5757 int
5758 SetCharTable (char *table, const char * map)
5759 /* [HGM] moved here from winboard.c because of its general usefulness */
5760 /*       Basically a safe strcpy that uses the last character as King */
5761 {
5762     int result = FALSE; int NrPieces;
5763
5764     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5765                     && NrPieces >= 12 && !(NrPieces&1)) {
5766         int i; /* [HGM] Accept even length from 12 to 34 */
5767
5768         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5769         for( i=0; i<NrPieces/2-1; i++ ) {
5770             table[i] = map[i];
5771             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5772         }
5773         table[(int) WhiteKing]  = map[NrPieces/2-1];
5774         table[(int) BlackKing]  = map[NrPieces-1];
5775
5776         result = TRUE;
5777     }
5778
5779     return result;
5780 }
5781
5782 void
5783 Prelude (Board board)
5784 {       // [HGM] superchess: random selection of exo-pieces
5785         int i, j, k; ChessSquare p;
5786         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5787
5788         GetPositionNumber(); // use FRC position number
5789
5790         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5791             SetCharTable(pieceToChar, appData.pieceToCharTable);
5792             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5793                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5794         }
5795
5796         j = seed%4;                 seed /= 4;
5797         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5798         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5799         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5800         j = seed%3 + (seed%3 >= j); seed /= 3;
5801         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5802         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5803         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5804         j = seed%3;                 seed /= 3;
5805         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5806         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5807         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5808         j = seed%2 + (seed%2 >= j); seed /= 2;
5809         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5810         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5811         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5812         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5813         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5814         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5815         put(board, exoPieces[0],    0, 0, ANY);
5816         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5817 }
5818
5819 void
5820 InitPosition (int redraw)
5821 {
5822     ChessSquare (* pieces)[BOARD_FILES];
5823     int i, j, pawnRow, overrule,
5824     oldx = gameInfo.boardWidth,
5825     oldy = gameInfo.boardHeight,
5826     oldh = gameInfo.holdingsWidth;
5827     static int oldv;
5828
5829     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5830
5831     /* [AS] Initialize pv info list [HGM] and game status */
5832     {
5833         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5834             pvInfoList[i].depth = 0;
5835             boards[i][EP_STATUS] = EP_NONE;
5836             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5837         }
5838
5839         initialRulePlies = 0; /* 50-move counter start */
5840
5841         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5842         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5843     }
5844
5845
5846     /* [HGM] logic here is completely changed. In stead of full positions */
5847     /* the initialized data only consist of the two backranks. The switch */
5848     /* selects which one we will use, which is than copied to the Board   */
5849     /* initialPosition, which for the rest is initialized by Pawns and    */
5850     /* empty squares. This initial position is then copied to boards[0],  */
5851     /* possibly after shuffling, so that it remains available.            */
5852
5853     gameInfo.holdingsWidth = 0; /* default board sizes */
5854     gameInfo.boardWidth    = 8;
5855     gameInfo.boardHeight   = 8;
5856     gameInfo.holdingsSize  = 0;
5857     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5858     for(i=0; i<BOARD_FILES-2; i++)
5859       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5860     initialPosition[EP_STATUS] = EP_NONE;
5861     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5862     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5863          SetCharTable(pieceNickName, appData.pieceNickNames);
5864     else SetCharTable(pieceNickName, "............");
5865     pieces = FIDEArray;
5866
5867     switch (gameInfo.variant) {
5868     case VariantFischeRandom:
5869       shuffleOpenings = TRUE;
5870     default:
5871       break;
5872     case VariantShatranj:
5873       pieces = ShatranjArray;
5874       nrCastlingRights = 0;
5875       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5876       break;
5877     case VariantMakruk:
5878       pieces = makrukArray;
5879       nrCastlingRights = 0;
5880       startedFromSetupPosition = TRUE;
5881       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5882       break;
5883     case VariantTwoKings:
5884       pieces = twoKingsArray;
5885       break;
5886     case VariantGrand:
5887       pieces = GrandArray;
5888       nrCastlingRights = 0;
5889       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5890       gameInfo.boardWidth = 10;
5891       gameInfo.boardHeight = 10;
5892       gameInfo.holdingsSize = 7;
5893       break;
5894     case VariantCapaRandom:
5895       shuffleOpenings = TRUE;
5896     case VariantCapablanca:
5897       pieces = CapablancaArray;
5898       gameInfo.boardWidth = 10;
5899       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5900       break;
5901     case VariantGothic:
5902       pieces = GothicArray;
5903       gameInfo.boardWidth = 10;
5904       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5905       break;
5906     case VariantSChess:
5907       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5908       gameInfo.holdingsSize = 7;
5909       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5910       break;
5911     case VariantJanus:
5912       pieces = JanusArray;
5913       gameInfo.boardWidth = 10;
5914       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5915       nrCastlingRights = 6;
5916         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5917         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5918         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5919         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5920         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5921         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5922       break;
5923     case VariantFalcon:
5924       pieces = FalconArray;
5925       gameInfo.boardWidth = 10;
5926       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5927       break;
5928     case VariantXiangqi:
5929       pieces = XiangqiArray;
5930       gameInfo.boardWidth  = 9;
5931       gameInfo.boardHeight = 10;
5932       nrCastlingRights = 0;
5933       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5934       break;
5935     case VariantShogi:
5936       pieces = ShogiArray;
5937       gameInfo.boardWidth  = 9;
5938       gameInfo.boardHeight = 9;
5939       gameInfo.holdingsSize = 7;
5940       nrCastlingRights = 0;
5941       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5942       break;
5943     case VariantCourier:
5944       pieces = CourierArray;
5945       gameInfo.boardWidth  = 12;
5946       nrCastlingRights = 0;
5947       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5948       break;
5949     case VariantKnightmate:
5950       pieces = KnightmateArray;
5951       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5952       break;
5953     case VariantSpartan:
5954       pieces = SpartanArray;
5955       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5956       break;
5957     case VariantFairy:
5958       pieces = fairyArray;
5959       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5960       break;
5961     case VariantGreat:
5962       pieces = GreatArray;
5963       gameInfo.boardWidth = 10;
5964       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5965       gameInfo.holdingsSize = 8;
5966       break;
5967     case VariantSuper:
5968       pieces = FIDEArray;
5969       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5970       gameInfo.holdingsSize = 8;
5971       startedFromSetupPosition = TRUE;
5972       break;
5973     case VariantCrazyhouse:
5974     case VariantBughouse:
5975       pieces = FIDEArray;
5976       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5977       gameInfo.holdingsSize = 5;
5978       break;
5979     case VariantWildCastle:
5980       pieces = FIDEArray;
5981       /* !!?shuffle with kings guaranteed to be on d or e file */
5982       shuffleOpenings = 1;
5983       break;
5984     case VariantNoCastle:
5985       pieces = FIDEArray;
5986       nrCastlingRights = 0;
5987       /* !!?unconstrained back-rank shuffle */
5988       shuffleOpenings = 1;
5989       break;
5990     }
5991
5992     overrule = 0;
5993     if(appData.NrFiles >= 0) {
5994         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5995         gameInfo.boardWidth = appData.NrFiles;
5996     }
5997     if(appData.NrRanks >= 0) {
5998         gameInfo.boardHeight = appData.NrRanks;
5999     }
6000     if(appData.holdingsSize >= 0) {
6001         i = appData.holdingsSize;
6002         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6003         gameInfo.holdingsSize = i;
6004     }
6005     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6006     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6007         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6008
6009     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6010     if(pawnRow < 1) pawnRow = 1;
6011     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6012
6013     /* User pieceToChar list overrules defaults */
6014     if(appData.pieceToCharTable != NULL)
6015         SetCharTable(pieceToChar, appData.pieceToCharTable);
6016
6017     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6018
6019         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6020             s = (ChessSquare) 0; /* account holding counts in guard band */
6021         for( i=0; i<BOARD_HEIGHT; i++ )
6022             initialPosition[i][j] = s;
6023
6024         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6025         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6026         initialPosition[pawnRow][j] = WhitePawn;
6027         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6028         if(gameInfo.variant == VariantXiangqi) {
6029             if(j&1) {
6030                 initialPosition[pawnRow][j] =
6031                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6032                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6033                    initialPosition[2][j] = WhiteCannon;
6034                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6035                 }
6036             }
6037         }
6038         if(gameInfo.variant == VariantGrand) {
6039             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6040                initialPosition[0][j] = WhiteRook;
6041                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6042             }
6043         }
6044         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6045     }
6046     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6047
6048             j=BOARD_LEFT+1;
6049             initialPosition[1][j] = WhiteBishop;
6050             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6051             j=BOARD_RGHT-2;
6052             initialPosition[1][j] = WhiteRook;
6053             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6054     }
6055
6056     if( nrCastlingRights == -1) {
6057         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6058         /*       This sets default castling rights from none to normal corners   */
6059         /* Variants with other castling rights must set them themselves above    */
6060         nrCastlingRights = 6;
6061
6062         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6063         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6064         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6065         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6066         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6067         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6068      }
6069
6070      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6071      if(gameInfo.variant == VariantGreat) { // promotion commoners
6072         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6073         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6074         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6075         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6076      }
6077      if( gameInfo.variant == VariantSChess ) {
6078       initialPosition[1][0] = BlackMarshall;
6079       initialPosition[2][0] = BlackAngel;
6080       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6081       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6082       initialPosition[1][1] = initialPosition[2][1] = 
6083       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6084      }
6085   if (appData.debugMode) {
6086     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6087   }
6088     if(shuffleOpenings) {
6089         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6090         startedFromSetupPosition = TRUE;
6091     }
6092     if(startedFromPositionFile) {
6093       /* [HGM] loadPos: use PositionFile for every new game */
6094       CopyBoard(initialPosition, filePosition);
6095       for(i=0; i<nrCastlingRights; i++)
6096           initialRights[i] = filePosition[CASTLING][i];
6097       startedFromSetupPosition = TRUE;
6098     }
6099
6100     CopyBoard(boards[0], initialPosition);
6101
6102     if(oldx != gameInfo.boardWidth ||
6103        oldy != gameInfo.boardHeight ||
6104        oldv != gameInfo.variant ||
6105        oldh != gameInfo.holdingsWidth
6106                                          )
6107             InitDrawingSizes(-2 ,0);
6108
6109     oldv = gameInfo.variant;
6110     if (redraw)
6111       DrawPosition(TRUE, boards[currentMove]);
6112 }
6113
6114 void
6115 SendBoard (ChessProgramState *cps, int moveNum)
6116 {
6117     char message[MSG_SIZ];
6118
6119     if (cps->useSetboard) {
6120       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6121       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6122       SendToProgram(message, cps);
6123       free(fen);
6124
6125     } else {
6126       ChessSquare *bp;
6127       int i, j, left=0, right=BOARD_WIDTH;
6128       /* Kludge to set black to move, avoiding the troublesome and now
6129        * deprecated "black" command.
6130        */
6131       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6132         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6133
6134       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6135
6136       SendToProgram("edit\n", cps);
6137       SendToProgram("#\n", cps);
6138       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6139         bp = &boards[moveNum][i][left];
6140         for (j = left; j < right; j++, bp++) {
6141           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6142           if ((int) *bp < (int) BlackPawn) {
6143             if(j == BOARD_RGHT+1)
6144                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6145             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6146             if(message[0] == '+' || message[0] == '~') {
6147               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6148                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6149                         AAA + j, ONE + i);
6150             }
6151             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6152                 message[1] = BOARD_RGHT   - 1 - j + '1';
6153                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6154             }
6155             SendToProgram(message, cps);
6156           }
6157         }
6158       }
6159
6160       SendToProgram("c\n", cps);
6161       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6162         bp = &boards[moveNum][i][left];
6163         for (j = left; j < right; j++, bp++) {
6164           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6165           if (((int) *bp != (int) EmptySquare)
6166               && ((int) *bp >= (int) BlackPawn)) {
6167             if(j == BOARD_LEFT-2)
6168                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6169             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6170                     AAA + j, ONE + i);
6171             if(message[0] == '+' || message[0] == '~') {
6172               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6173                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6174                         AAA + j, ONE + i);
6175             }
6176             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6177                 message[1] = BOARD_RGHT   - 1 - j + '1';
6178                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6179             }
6180             SendToProgram(message, cps);
6181           }
6182         }
6183       }
6184
6185       SendToProgram(".\n", cps);
6186     }
6187     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6188 }
6189
6190 char exclusionHeader[MSG_SIZ];
6191 int exCnt, excludePtr;
6192 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6193 static Exclusion excluTab[200];
6194 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6195
6196 static void
6197 WriteMap (int s)
6198 {
6199     int j;
6200     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6201     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6202 }
6203
6204 static void
6205 ClearMap ()
6206 {
6207     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6208     excludePtr = 24; exCnt = 0;
6209     WriteMap(0);
6210 }
6211
6212 static void
6213 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6214 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6215     char buf[2*MOVE_LEN], *p;
6216     Exclusion *e = excluTab;
6217     int i;
6218     for(i=0; i<exCnt; i++)
6219         if(e[i].ff == fromX && e[i].fr == fromY &&
6220            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6221     if(i == exCnt) { // was not in exclude list; add it
6222         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6223         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6224             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6225             return; // abort
6226         }
6227         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6228         excludePtr++; e[i].mark = excludePtr++;
6229         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6230         exCnt++;
6231     }
6232     exclusionHeader[e[i].mark] = state;
6233 }
6234
6235 static int
6236 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6237 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6238     char buf[MSG_SIZ];
6239     int j, k;
6240     ChessMove moveType;
6241     if((signed char)promoChar == -1) { // kludge to indicate best move
6242         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6243             return 1; // if unparsable, abort
6244     }
6245     // update exclusion map (resolving toggle by consulting existing state)
6246     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6247     j = k%8; k >>= 3;
6248     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6249     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6250          excludeMap[k] |=   1<<j;
6251     else excludeMap[k] &= ~(1<<j);
6252     // update header
6253     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6254     // inform engine
6255     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6256     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6257     SendToBoth(buf);
6258     return (state == '+');
6259 }
6260
6261 static void
6262 ExcludeClick (int index)
6263 {
6264     int i, j;
6265     Exclusion *e = excluTab;
6266     if(index < 25) { // none, best or tail clicked
6267         if(index < 13) { // none: include all
6268             WriteMap(0); // clear map
6269             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6270             SendToBoth("include all\n"); // and inform engine
6271         } else if(index > 18) { // tail
6272             if(exclusionHeader[19] == '-') { // tail was excluded
6273                 SendToBoth("include all\n");
6274                 WriteMap(0); // clear map completely
6275                 // now re-exclude selected moves
6276                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6277                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6278             } else { // tail was included or in mixed state
6279                 SendToBoth("exclude all\n");
6280                 WriteMap(0xFF); // fill map completely
6281                 // now re-include selected moves
6282                 j = 0; // count them
6283                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6284                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6285                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6286             }
6287         } else { // best
6288             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6289         }
6290     } else {
6291         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6292             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6293             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6294             break;
6295         }
6296     }
6297 }
6298
6299 ChessSquare
6300 DefaultPromoChoice (int white)
6301 {
6302     ChessSquare result;
6303     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6304         result = WhiteFerz; // no choice
6305     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6306         result= WhiteKing; // in Suicide Q is the last thing we want
6307     else if(gameInfo.variant == VariantSpartan)
6308         result = white ? WhiteQueen : WhiteAngel;
6309     else result = WhiteQueen;
6310     if(!white) result = WHITE_TO_BLACK result;
6311     return result;
6312 }
6313
6314 static int autoQueen; // [HGM] oneclick
6315
6316 int
6317 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6318 {
6319     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6320     /* [HGM] add Shogi promotions */
6321     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6322     ChessSquare piece;
6323     ChessMove moveType;
6324     Boolean premove;
6325
6326     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6327     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6328
6329     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6330       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6331         return FALSE;
6332
6333     piece = boards[currentMove][fromY][fromX];
6334     if(gameInfo.variant == VariantShogi) {
6335         promotionZoneSize = BOARD_HEIGHT/3;
6336         highestPromotingPiece = (int)WhiteFerz;
6337     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6338         promotionZoneSize = 3;
6339     }
6340
6341     // Treat Lance as Pawn when it is not representing Amazon
6342     if(gameInfo.variant != VariantSuper) {
6343         if(piece == WhiteLance) piece = WhitePawn; else
6344         if(piece == BlackLance) piece = BlackPawn;
6345     }
6346
6347     // next weed out all moves that do not touch the promotion zone at all
6348     if((int)piece >= BlackPawn) {
6349         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6350              return FALSE;
6351         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6352     } else {
6353         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6354            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6355     }
6356
6357     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6358
6359     // weed out mandatory Shogi promotions
6360     if(gameInfo.variant == VariantShogi) {
6361         if(piece >= BlackPawn) {
6362             if(toY == 0 && piece == BlackPawn ||
6363                toY == 0 && piece == BlackQueen ||
6364                toY <= 1 && piece == BlackKnight) {
6365                 *promoChoice = '+';
6366                 return FALSE;
6367             }
6368         } else {
6369             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6370                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6371                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6372                 *promoChoice = '+';
6373                 return FALSE;
6374             }
6375         }
6376     }
6377
6378     // weed out obviously illegal Pawn moves
6379     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6380         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6381         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6382         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6383         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6384         // note we are not allowed to test for valid (non-)capture, due to premove
6385     }
6386
6387     // we either have a choice what to promote to, or (in Shogi) whether to promote
6388     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6389         *promoChoice = PieceToChar(BlackFerz);  // no choice
6390         return FALSE;
6391     }
6392     // no sense asking what we must promote to if it is going to explode...
6393     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6394         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6395         return FALSE;
6396     }
6397     // give caller the default choice even if we will not make it
6398     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6399     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6400     if(        sweepSelect && gameInfo.variant != VariantGreat
6401                            && gameInfo.variant != VariantGrand
6402                            && gameInfo.variant != VariantSuper) return FALSE;
6403     if(autoQueen) return FALSE; // predetermined
6404
6405     // suppress promotion popup on illegal moves that are not premoves
6406     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6407               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6408     if(appData.testLegality && !premove) {
6409         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6410                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6411         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6412             return FALSE;
6413     }
6414
6415     return TRUE;
6416 }
6417
6418 int
6419 InPalace (int row, int column)
6420 {   /* [HGM] for Xiangqi */
6421     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6422          column < (BOARD_WIDTH + 4)/2 &&
6423          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6424     return FALSE;
6425 }
6426
6427 int
6428 PieceForSquare (int x, int y)
6429 {
6430   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6431      return -1;
6432   else
6433      return boards[currentMove][y][x];
6434 }
6435
6436 int
6437 OKToStartUserMove (int x, int y)
6438 {
6439     ChessSquare from_piece;
6440     int white_piece;
6441
6442     if (matchMode) return FALSE;
6443     if (gameMode == EditPosition) return TRUE;
6444
6445     if (x >= 0 && y >= 0)
6446       from_piece = boards[currentMove][y][x];
6447     else
6448       from_piece = EmptySquare;
6449
6450     if (from_piece == EmptySquare) return FALSE;
6451
6452     white_piece = (int)from_piece >= (int)WhitePawn &&
6453       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6454
6455     switch (gameMode) {
6456       case AnalyzeFile:
6457       case TwoMachinesPlay:
6458       case EndOfGame:
6459         return FALSE;
6460
6461       case IcsObserving:
6462       case IcsIdle:
6463         return FALSE;
6464
6465       case MachinePlaysWhite:
6466       case IcsPlayingBlack:
6467         if (appData.zippyPlay) return FALSE;
6468         if (white_piece) {
6469             DisplayMoveError(_("You are playing Black"));
6470             return FALSE;
6471         }
6472         break;
6473
6474       case MachinePlaysBlack:
6475       case IcsPlayingWhite:
6476         if (appData.zippyPlay) return FALSE;
6477         if (!white_piece) {
6478             DisplayMoveError(_("You are playing White"));
6479             return FALSE;
6480         }
6481         break;
6482
6483       case PlayFromGameFile:
6484             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6485       case EditGame:
6486         if (!white_piece && WhiteOnMove(currentMove)) {
6487             DisplayMoveError(_("It is White's turn"));
6488             return FALSE;
6489         }
6490         if (white_piece && !WhiteOnMove(currentMove)) {
6491             DisplayMoveError(_("It is Black's turn"));
6492             return FALSE;
6493         }
6494         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6495             /* Editing correspondence game history */
6496             /* Could disallow this or prompt for confirmation */
6497             cmailOldMove = -1;
6498         }
6499         break;
6500
6501       case BeginningOfGame:
6502         if (appData.icsActive) return FALSE;
6503         if (!appData.noChessProgram) {
6504             if (!white_piece) {
6505                 DisplayMoveError(_("You are playing White"));
6506                 return FALSE;
6507             }
6508         }
6509         break;
6510
6511       case Training:
6512         if (!white_piece && WhiteOnMove(currentMove)) {
6513             DisplayMoveError(_("It is White's turn"));
6514             return FALSE;
6515         }
6516         if (white_piece && !WhiteOnMove(currentMove)) {
6517             DisplayMoveError(_("It is Black's turn"));
6518             return FALSE;
6519         }
6520         break;
6521
6522       default:
6523       case IcsExamining:
6524         break;
6525     }
6526     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6527         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6528         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6529         && gameMode != AnalyzeFile && gameMode != Training) {
6530         DisplayMoveError(_("Displayed position is not current"));
6531         return FALSE;
6532     }
6533     return TRUE;
6534 }
6535
6536 Boolean
6537 OnlyMove (int *x, int *y, Boolean captures) 
6538 {
6539     DisambiguateClosure cl;
6540     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6541     switch(gameMode) {
6542       case MachinePlaysBlack:
6543       case IcsPlayingWhite:
6544       case BeginningOfGame:
6545         if(!WhiteOnMove(currentMove)) return FALSE;
6546         break;
6547       case MachinePlaysWhite:
6548       case IcsPlayingBlack:
6549         if(WhiteOnMove(currentMove)) return FALSE;
6550         break;
6551       case EditGame:
6552         break;
6553       default:
6554         return FALSE;
6555     }
6556     cl.pieceIn = EmptySquare;
6557     cl.rfIn = *y;
6558     cl.ffIn = *x;
6559     cl.rtIn = -1;
6560     cl.ftIn = -1;
6561     cl.promoCharIn = NULLCHAR;
6562     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6563     if( cl.kind == NormalMove ||
6564         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6565         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6566         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6567       fromX = cl.ff;
6568       fromY = cl.rf;
6569       *x = cl.ft;
6570       *y = cl.rt;
6571       return TRUE;
6572     }
6573     if(cl.kind != ImpossibleMove) return FALSE;
6574     cl.pieceIn = EmptySquare;
6575     cl.rfIn = -1;
6576     cl.ffIn = -1;
6577     cl.rtIn = *y;
6578     cl.ftIn = *x;
6579     cl.promoCharIn = NULLCHAR;
6580     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6581     if( cl.kind == NormalMove ||
6582         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6583         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6584         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6585       fromX = cl.ff;
6586       fromY = cl.rf;
6587       *x = cl.ft;
6588       *y = cl.rt;
6589       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6590       return TRUE;
6591     }
6592     return FALSE;
6593 }
6594
6595 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6596 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6597 int lastLoadGameUseList = FALSE;
6598 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6599 ChessMove lastLoadGameStart = EndOfFile;
6600 int doubleClick;
6601
6602 void
6603 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6604 {
6605     ChessMove moveType;
6606     ChessSquare pup;
6607     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6608
6609     /* Check if the user is playing in turn.  This is complicated because we
6610        let the user "pick up" a piece before it is his turn.  So the piece he
6611        tried to pick up may have been captured by the time he puts it down!
6612        Therefore we use the color the user is supposed to be playing in this
6613        test, not the color of the piece that is currently on the starting
6614        square---except in EditGame mode, where the user is playing both
6615        sides; fortunately there the capture race can't happen.  (It can
6616        now happen in IcsExamining mode, but that's just too bad.  The user
6617        will get a somewhat confusing message in that case.)
6618        */
6619
6620     switch (gameMode) {
6621       case AnalyzeFile:
6622       case TwoMachinesPlay:
6623       case EndOfGame:
6624       case IcsObserving:
6625       case IcsIdle:
6626         /* We switched into a game mode where moves are not accepted,
6627            perhaps while the mouse button was down. */
6628         return;
6629
6630       case MachinePlaysWhite:
6631         /* User is moving for Black */
6632         if (WhiteOnMove(currentMove)) {
6633             DisplayMoveError(_("It is White's turn"));
6634             return;
6635         }
6636         break;
6637
6638       case MachinePlaysBlack:
6639         /* User is moving for White */
6640         if (!WhiteOnMove(currentMove)) {
6641             DisplayMoveError(_("It is Black's turn"));
6642             return;
6643         }
6644         break;
6645
6646       case PlayFromGameFile:
6647             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6648       case EditGame:
6649       case IcsExamining:
6650       case BeginningOfGame:
6651       case AnalyzeMode:
6652       case Training:
6653         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6654         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6655             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6656             /* User is moving for Black */
6657             if (WhiteOnMove(currentMove)) {
6658                 DisplayMoveError(_("It is White's turn"));
6659                 return;
6660             }
6661         } else {
6662             /* User is moving for White */
6663             if (!WhiteOnMove(currentMove)) {
6664                 DisplayMoveError(_("It is Black's turn"));
6665                 return;
6666             }
6667         }
6668         break;
6669
6670       case IcsPlayingBlack:
6671         /* User is moving for Black */
6672         if (WhiteOnMove(currentMove)) {
6673             if (!appData.premove) {
6674                 DisplayMoveError(_("It is White's turn"));
6675             } else if (toX >= 0 && toY >= 0) {
6676                 premoveToX = toX;
6677                 premoveToY = toY;
6678                 premoveFromX = fromX;
6679                 premoveFromY = fromY;
6680                 premovePromoChar = promoChar;
6681                 gotPremove = 1;
6682                 if (appData.debugMode)
6683                     fprintf(debugFP, "Got premove: fromX %d,"
6684                             "fromY %d, toX %d, toY %d\n",
6685                             fromX, fromY, toX, toY);
6686             }
6687             return;
6688         }
6689         break;
6690
6691       case IcsPlayingWhite:
6692         /* User is moving for White */
6693         if (!WhiteOnMove(currentMove)) {
6694             if (!appData.premove) {
6695                 DisplayMoveError(_("It is Black's turn"));
6696             } else if (toX >= 0 && toY >= 0) {
6697                 premoveToX = toX;
6698                 premoveToY = toY;
6699                 premoveFromX = fromX;
6700                 premoveFromY = fromY;
6701                 premovePromoChar = promoChar;
6702                 gotPremove = 1;
6703                 if (appData.debugMode)
6704                     fprintf(debugFP, "Got premove: fromX %d,"
6705                             "fromY %d, toX %d, toY %d\n",
6706                             fromX, fromY, toX, toY);
6707             }
6708             return;
6709         }
6710         break;
6711
6712       default:
6713         break;
6714
6715       case EditPosition:
6716         /* EditPosition, empty square, or different color piece;
6717            click-click move is possible */
6718         if (toX == -2 || toY == -2) {
6719             boards[0][fromY][fromX] = EmptySquare;
6720             DrawPosition(FALSE, boards[currentMove]);
6721             return;
6722         } else if (toX >= 0 && toY >= 0) {
6723             boards[0][toY][toX] = boards[0][fromY][fromX];
6724             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6725                 if(boards[0][fromY][0] != EmptySquare) {
6726                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6727                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6728                 }
6729             } else
6730             if(fromX == BOARD_RGHT+1) {
6731                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6732                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6733                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6734                 }
6735             } else
6736             boards[0][fromY][fromX] = gatingPiece;
6737             DrawPosition(FALSE, boards[currentMove]);
6738             return;
6739         }
6740         return;
6741     }
6742
6743     if(toX < 0 || toY < 0) return;
6744     pup = boards[currentMove][toY][toX];
6745
6746     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6747     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6748          if( pup != EmptySquare ) return;
6749          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6750            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6751                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6752            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6753            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6754            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6755            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6756          fromY = DROP_RANK;
6757     }
6758
6759     /* [HGM] always test for legality, to get promotion info */
6760     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6761                                          fromY, fromX, toY, toX, promoChar);
6762
6763     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6764
6765     /* [HGM] but possibly ignore an IllegalMove result */
6766     if (appData.testLegality) {
6767         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6768             DisplayMoveError(_("Illegal move"));
6769             return;
6770         }
6771     }
6772
6773     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6774         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6775              ClearPremoveHighlights(); // was included
6776         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6777         return;
6778     }
6779
6780     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6781 }
6782
6783 /* Common tail of UserMoveEvent and DropMenuEvent */
6784 int
6785 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6786 {
6787     char *bookHit = 0;
6788
6789     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6790         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6791         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6792         if(WhiteOnMove(currentMove)) {
6793             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6794         } else {
6795             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6796         }
6797     }
6798
6799     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6800        move type in caller when we know the move is a legal promotion */
6801     if(moveType == NormalMove && promoChar)
6802         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6803
6804     /* [HGM] <popupFix> The following if has been moved here from
6805        UserMoveEvent(). Because it seemed to belong here (why not allow
6806        piece drops in training games?), and because it can only be
6807        performed after it is known to what we promote. */
6808     if (gameMode == Training) {
6809       /* compare the move played on the board to the next move in the
6810        * game. If they match, display the move and the opponent's response.
6811        * If they don't match, display an error message.
6812        */
6813       int saveAnimate;
6814       Board testBoard;
6815       CopyBoard(testBoard, boards[currentMove]);
6816       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6817
6818       if (CompareBoards(testBoard, boards[currentMove+1])) {
6819         ForwardInner(currentMove+1);
6820
6821         /* Autoplay the opponent's response.
6822          * if appData.animate was TRUE when Training mode was entered,
6823          * the response will be animated.
6824          */
6825         saveAnimate = appData.animate;
6826         appData.animate = animateTraining;
6827         ForwardInner(currentMove+1);
6828         appData.animate = saveAnimate;
6829
6830         /* check for the end of the game */
6831         if (currentMove >= forwardMostMove) {
6832           gameMode = PlayFromGameFile;
6833           ModeHighlight();
6834           SetTrainingModeOff();
6835           DisplayInformation(_("End of game"));
6836         }
6837       } else {
6838         DisplayError(_("Incorrect move"), 0);
6839       }
6840       return 1;
6841     }
6842
6843   /* Ok, now we know that the move is good, so we can kill
6844      the previous line in Analysis Mode */
6845   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6846                                 && currentMove < forwardMostMove) {
6847     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6848     else forwardMostMove = currentMove;
6849   }
6850
6851   ClearMap();
6852
6853   /* If we need the chess program but it's dead, restart it */
6854   ResurrectChessProgram();
6855
6856   /* A user move restarts a paused game*/
6857   if (pausing)
6858     PauseEvent();
6859
6860   thinkOutput[0] = NULLCHAR;
6861
6862   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6863
6864   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6865     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6866     return 1;
6867   }
6868
6869   if (gameMode == BeginningOfGame) {
6870     if (appData.noChessProgram) {
6871       gameMode = EditGame;
6872       SetGameInfo();
6873     } else {
6874       char buf[MSG_SIZ];
6875       gameMode = MachinePlaysBlack;
6876       StartClocks();
6877       SetGameInfo();
6878       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6879       DisplayTitle(buf);
6880       if (first.sendName) {
6881         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6882         SendToProgram(buf, &first);
6883       }
6884       StartClocks();
6885     }
6886     ModeHighlight();
6887   }
6888
6889   /* Relay move to ICS or chess engine */
6890   if (appData.icsActive) {
6891     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6892         gameMode == IcsExamining) {
6893       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6894         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6895         SendToICS("draw ");
6896         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6897       }
6898       // also send plain move, in case ICS does not understand atomic claims
6899       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6900       ics_user_moved = 1;
6901     }
6902   } else {
6903     if (first.sendTime && (gameMode == BeginningOfGame ||
6904                            gameMode == MachinePlaysWhite ||
6905                            gameMode == MachinePlaysBlack)) {
6906       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6907     }
6908     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6909          // [HGM] book: if program might be playing, let it use book
6910         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6911         first.maybeThinking = TRUE;
6912     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6913         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6914         SendBoard(&first, currentMove+1);
6915         if(second.analyzing) {
6916             if(!second.useSetboard) SendToProgram("undo\n", &second);
6917             SendBoard(&second, currentMove+1);
6918         }
6919     } else {
6920         SendMoveToProgram(forwardMostMove-1, &first);
6921         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6922     }
6923     if (currentMove == cmailOldMove + 1) {
6924       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6925     }
6926   }
6927
6928   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6929
6930   switch (gameMode) {
6931   case EditGame:
6932     if(appData.testLegality)
6933     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6934     case MT_NONE:
6935     case MT_CHECK:
6936       break;
6937     case MT_CHECKMATE:
6938     case MT_STAINMATE:
6939       if (WhiteOnMove(currentMove)) {
6940         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6941       } else {
6942         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6943       }
6944       break;
6945     case MT_STALEMATE:
6946       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6947       break;
6948     }
6949     break;
6950
6951   case MachinePlaysBlack:
6952   case MachinePlaysWhite:
6953     /* disable certain menu options while machine is thinking */
6954     SetMachineThinkingEnables();
6955     break;
6956
6957   default:
6958     break;
6959   }
6960
6961   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6962   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6963
6964   if(bookHit) { // [HGM] book: simulate book reply
6965         static char bookMove[MSG_SIZ]; // a bit generous?
6966
6967         programStats.nodes = programStats.depth = programStats.time =
6968         programStats.score = programStats.got_only_move = 0;
6969         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6970
6971         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6972         strcat(bookMove, bookHit);
6973         HandleMachineMove(bookMove, &first);
6974   }
6975   return 1;
6976 }
6977
6978 void
6979 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6980 {
6981     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6982     Markers *m = (Markers *) closure;
6983     if(rf == fromY && ff == fromX)
6984         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6985                          || kind == WhiteCapturesEnPassant
6986                          || kind == BlackCapturesEnPassant);
6987     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6988 }
6989
6990 void
6991 MarkTargetSquares (int clear)
6992 {
6993   int x, y;
6994   if(clear) // no reason to ever suppress clearing
6995     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6996   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6997      !appData.testLegality || gameMode == EditPosition) return;
6998   if(!clear) {
6999     int capt = 0;
7000     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7001     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7002       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7003       if(capt)
7004       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7005     }
7006   }
7007   DrawPosition(FALSE, NULL);
7008 }
7009
7010 int
7011 Explode (Board board, int fromX, int fromY, int toX, int toY)
7012 {
7013     if(gameInfo.variant == VariantAtomic &&
7014        (board[toY][toX] != EmptySquare ||                     // capture?
7015         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7016                          board[fromY][fromX] == BlackPawn   )
7017       )) {
7018         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7019         return TRUE;
7020     }
7021     return FALSE;
7022 }
7023
7024 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7025
7026 int
7027 CanPromote (ChessSquare piece, int y)
7028 {
7029         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7030         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7031         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7032            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7033            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7034                                                   gameInfo.variant == VariantMakruk) return FALSE;
7035         return (piece == BlackPawn && y == 1 ||
7036                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7037                 piece == BlackLance && y == 1 ||
7038                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7039 }
7040
7041 void
7042 LeftClick (ClickType clickType, int xPix, int yPix)
7043 {
7044     int x, y;
7045     Boolean saveAnimate;
7046     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7047     char promoChoice = NULLCHAR;
7048     ChessSquare piece;
7049     static TimeMark lastClickTime, prevClickTime;
7050
7051     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7052
7053     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7054
7055     if (clickType == Press) ErrorPopDown();
7056
7057     x = EventToSquare(xPix, BOARD_WIDTH);
7058     y = EventToSquare(yPix, BOARD_HEIGHT);
7059     if (!flipView && y >= 0) {
7060         y = BOARD_HEIGHT - 1 - y;
7061     }
7062     if (flipView && x >= 0) {
7063         x = BOARD_WIDTH - 1 - x;
7064     }
7065
7066     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7067         defaultPromoChoice = promoSweep;
7068         promoSweep = EmptySquare;   // terminate sweep
7069         promoDefaultAltered = TRUE;
7070         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7071     }
7072
7073     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7074         if(clickType == Release) return; // ignore upclick of click-click destination
7075         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7076         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7077         if(gameInfo.holdingsWidth &&
7078                 (WhiteOnMove(currentMove)
7079                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7080                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7081             // click in right holdings, for determining promotion piece
7082             ChessSquare p = boards[currentMove][y][x];
7083             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7084             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7085             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7086                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7087                 fromX = fromY = -1;
7088                 return;
7089             }
7090         }
7091         DrawPosition(FALSE, boards[currentMove]);
7092         return;
7093     }
7094
7095     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7096     if(clickType == Press
7097             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7098               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7099               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7100         return;
7101
7102     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7103         // could be static click on premove from-square: abort premove
7104         gotPremove = 0;
7105         ClearPremoveHighlights();
7106     }
7107
7108     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7109         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7110
7111     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7112         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7113                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7114         defaultPromoChoice = DefaultPromoChoice(side);
7115     }
7116
7117     autoQueen = appData.alwaysPromoteToQueen;
7118
7119     if (fromX == -1) {
7120       int originalY = y;
7121       gatingPiece = EmptySquare;
7122       if (clickType != Press) {
7123         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7124             DragPieceEnd(xPix, yPix); dragging = 0;
7125             DrawPosition(FALSE, NULL);
7126         }
7127         return;
7128       }
7129       doubleClick = FALSE;
7130       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7131         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7132       }
7133       fromX = x; fromY = y; toX = toY = -1;
7134       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7135          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7136          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7137             /* First square */
7138             if (OKToStartUserMove(fromX, fromY)) {
7139                 second = 0;
7140                 MarkTargetSquares(0);
7141                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7142                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7143                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7144                     promoSweep = defaultPromoChoice;
7145                     selectFlag = 0; lastX = xPix; lastY = yPix;
7146                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7147                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7148                 }
7149                 if (appData.highlightDragging) {
7150                     SetHighlights(fromX, fromY, -1, -1);
7151                 } else {
7152                     ClearHighlights();
7153                 }
7154             } else fromX = fromY = -1;
7155             return;
7156         }
7157     }
7158
7159     /* fromX != -1 */
7160     if (clickType == Press && gameMode != EditPosition) {
7161         ChessSquare fromP;
7162         ChessSquare toP;
7163         int frc;
7164
7165         // ignore off-board to clicks
7166         if(y < 0 || x < 0) return;
7167
7168         /* Check if clicking again on the same color piece */
7169         fromP = boards[currentMove][fromY][fromX];
7170         toP = boards[currentMove][y][x];
7171         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7172         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7173              WhitePawn <= toP && toP <= WhiteKing &&
7174              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7175              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7176             (BlackPawn <= fromP && fromP <= BlackKing &&
7177              BlackPawn <= toP && toP <= BlackKing &&
7178              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7179              !(fromP == BlackKing && toP == BlackRook && frc))) {
7180             /* Clicked again on same color piece -- changed his mind */
7181             second = (x == fromX && y == fromY);
7182             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7183                 second = FALSE; // first double-click rather than scond click
7184                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7185             }
7186             promoDefaultAltered = FALSE;
7187             MarkTargetSquares(1);
7188            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7189             if (appData.highlightDragging) {
7190                 SetHighlights(x, y, -1, -1);
7191             } else {
7192                 ClearHighlights();
7193             }
7194             if (OKToStartUserMove(x, y)) {
7195                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7196                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7197                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7198                  gatingPiece = boards[currentMove][fromY][fromX];
7199                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7200                 fromX = x;
7201                 fromY = y; dragging = 1;
7202                 MarkTargetSquares(0);
7203                 DragPieceBegin(xPix, yPix, FALSE);
7204                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7205                     promoSweep = defaultPromoChoice;
7206                     selectFlag = 0; lastX = xPix; lastY = yPix;
7207                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7208                 }
7209             }
7210            }
7211            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7212            second = FALSE; 
7213         }
7214         // ignore clicks on holdings
7215         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7216     }
7217
7218     if (clickType == Release && x == fromX && y == fromY) {
7219         DragPieceEnd(xPix, yPix); dragging = 0;
7220         if(clearFlag) {
7221             // a deferred attempt to click-click move an empty square on top of a piece
7222             boards[currentMove][y][x] = EmptySquare;
7223             ClearHighlights();
7224             DrawPosition(FALSE, boards[currentMove]);
7225             fromX = fromY = -1; clearFlag = 0;
7226             return;
7227         }
7228         if (appData.animateDragging) {
7229             /* Undo animation damage if any */
7230             DrawPosition(FALSE, NULL);
7231         }
7232         if (second || sweepSelecting) {
7233             /* Second up/down in same square; just abort move */
7234             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7235             second = sweepSelecting = 0;
7236             fromX = fromY = -1;
7237             gatingPiece = EmptySquare;
7238             ClearHighlights();
7239             gotPremove = 0;
7240             ClearPremoveHighlights();
7241         } else {
7242             /* First upclick in same square; start click-click mode */
7243             SetHighlights(x, y, -1, -1);
7244         }
7245         return;
7246     }
7247
7248     clearFlag = 0;
7249
7250     /* we now have a different from- and (possibly off-board) to-square */
7251     /* Completed move */
7252     if(!sweepSelecting) {
7253         toX = x;
7254         toY = y;
7255     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7256
7257     saveAnimate = appData.animate;
7258     if (clickType == Press) {
7259         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7260             // must be Edit Position mode with empty-square selected
7261             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7262             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7263             return;
7264         }
7265         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7266           if(appData.sweepSelect) {
7267             ChessSquare piece = boards[currentMove][fromY][fromX];
7268             promoSweep = defaultPromoChoice;
7269             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7270             selectFlag = 0; lastX = xPix; lastY = yPix;
7271             Sweep(0); // Pawn that is going to promote: preview promotion piece
7272             sweepSelecting = 1;
7273             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7274             MarkTargetSquares(1);
7275           }
7276           return; // promo popup appears on up-click
7277         }
7278         /* Finish clickclick move */
7279         if (appData.animate || appData.highlightLastMove) {
7280             SetHighlights(fromX, fromY, toX, toY);
7281         } else {
7282             ClearHighlights();
7283         }
7284     } else {
7285 #if 0
7286 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7287         /* Finish drag move */
7288         if (appData.highlightLastMove) {
7289             SetHighlights(fromX, fromY, toX, toY);
7290         } else {
7291             ClearHighlights();
7292         }
7293 #endif
7294         DragPieceEnd(xPix, yPix); dragging = 0;
7295         /* Don't animate move and drag both */
7296         appData.animate = FALSE;
7297     }
7298
7299     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7300     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7301         ChessSquare piece = boards[currentMove][fromY][fromX];
7302         if(gameMode == EditPosition && piece != EmptySquare &&
7303            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7304             int n;
7305
7306             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7307                 n = PieceToNumber(piece - (int)BlackPawn);
7308                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7309                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7310                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7311             } else
7312             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7313                 n = PieceToNumber(piece);
7314                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7315                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7316                 boards[currentMove][n][BOARD_WIDTH-2]++;
7317             }
7318             boards[currentMove][fromY][fromX] = EmptySquare;
7319         }
7320         ClearHighlights();
7321         fromX = fromY = -1;
7322         MarkTargetSquares(1);
7323         DrawPosition(TRUE, boards[currentMove]);
7324         return;
7325     }
7326
7327     // off-board moves should not be highlighted
7328     if(x < 0 || y < 0) ClearHighlights();
7329
7330     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7331
7332     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7333         SetHighlights(fromX, fromY, toX, toY);
7334         MarkTargetSquares(1);
7335         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7336             // [HGM] super: promotion to captured piece selected from holdings
7337             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7338             promotionChoice = TRUE;
7339             // kludge follows to temporarily execute move on display, without promoting yet
7340             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7341             boards[currentMove][toY][toX] = p;
7342             DrawPosition(FALSE, boards[currentMove]);
7343             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7344             boards[currentMove][toY][toX] = q;
7345             DisplayMessage("Click in holdings to choose piece", "");
7346             return;
7347         }
7348         PromotionPopUp();
7349     } else {
7350         int oldMove = currentMove;
7351         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7352         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7353         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7354         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7355            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7356             DrawPosition(TRUE, boards[currentMove]);
7357         MarkTargetSquares(1);
7358         fromX = fromY = -1;
7359     }
7360     appData.animate = saveAnimate;
7361     if (appData.animate || appData.animateDragging) {
7362         /* Undo animation damage if needed */
7363         DrawPosition(FALSE, NULL);
7364     }
7365 }
7366
7367 int
7368 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7369 {   // front-end-free part taken out of PieceMenuPopup
7370     int whichMenu; int xSqr, ySqr;
7371
7372     if(seekGraphUp) { // [HGM] seekgraph
7373         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7374         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7375         return -2;
7376     }
7377
7378     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7379          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7380         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7381         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7382         if(action == Press)   {
7383             originalFlip = flipView;
7384             flipView = !flipView; // temporarily flip board to see game from partners perspective
7385             DrawPosition(TRUE, partnerBoard);
7386             DisplayMessage(partnerStatus, "");
7387             partnerUp = TRUE;
7388         } else if(action == Release) {
7389             flipView = originalFlip;
7390             DrawPosition(TRUE, boards[currentMove]);
7391             partnerUp = FALSE;
7392         }
7393         return -2;
7394     }
7395
7396     xSqr = EventToSquare(x, BOARD_WIDTH);
7397     ySqr = EventToSquare(y, BOARD_HEIGHT);
7398     if (action == Release) {
7399         if(pieceSweep != EmptySquare) {
7400             EditPositionMenuEvent(pieceSweep, toX, toY);
7401             pieceSweep = EmptySquare;
7402         } else UnLoadPV(); // [HGM] pv
7403     }
7404     if (action != Press) return -2; // return code to be ignored
7405     switch (gameMode) {
7406       case IcsExamining:
7407         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7408       case EditPosition:
7409         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7410         if (xSqr < 0 || ySqr < 0) return -1;
7411         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7412         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7413         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7414         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7415         NextPiece(0);
7416         return 2; // grab
7417       case IcsObserving:
7418         if(!appData.icsEngineAnalyze) return -1;
7419       case IcsPlayingWhite:
7420       case IcsPlayingBlack:
7421         if(!appData.zippyPlay) goto noZip;
7422       case AnalyzeMode:
7423       case AnalyzeFile:
7424       case MachinePlaysWhite:
7425       case MachinePlaysBlack:
7426       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7427         if (!appData.dropMenu) {
7428           LoadPV(x, y);
7429           return 2; // flag front-end to grab mouse events
7430         }
7431         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7432            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7433       case EditGame:
7434       noZip:
7435         if (xSqr < 0 || ySqr < 0) return -1;
7436         if (!appData.dropMenu || appData.testLegality &&
7437             gameInfo.variant != VariantBughouse &&
7438             gameInfo.variant != VariantCrazyhouse) return -1;
7439         whichMenu = 1; // drop menu
7440         break;
7441       default:
7442         return -1;
7443     }
7444
7445     if (((*fromX = xSqr) < 0) ||
7446         ((*fromY = ySqr) < 0)) {
7447         *fromX = *fromY = -1;
7448         return -1;
7449     }
7450     if (flipView)
7451       *fromX = BOARD_WIDTH - 1 - *fromX;
7452     else
7453       *fromY = BOARD_HEIGHT - 1 - *fromY;
7454
7455     return whichMenu;
7456 }
7457
7458 void
7459 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7460 {
7461 //    char * hint = lastHint;
7462     FrontEndProgramStats stats;
7463
7464     stats.which = cps == &first ? 0 : 1;
7465     stats.depth = cpstats->depth;
7466     stats.nodes = cpstats->nodes;
7467     stats.score = cpstats->score;
7468     stats.time = cpstats->time;
7469     stats.pv = cpstats->movelist;
7470     stats.hint = lastHint;
7471     stats.an_move_index = 0;
7472     stats.an_move_count = 0;
7473
7474     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7475         stats.hint = cpstats->move_name;
7476         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7477         stats.an_move_count = cpstats->nr_moves;
7478     }
7479
7480     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
7481
7482     SetProgramStats( &stats );
7483 }
7484
7485 void
7486 ClearEngineOutputPane (int which)
7487 {
7488     static FrontEndProgramStats dummyStats;
7489     dummyStats.which = which;
7490     dummyStats.pv = "#";
7491     SetProgramStats( &dummyStats );
7492 }
7493
7494 #define MAXPLAYERS 500
7495
7496 char *
7497 TourneyStandings (int display)
7498 {
7499     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7500     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7501     char result, *p, *names[MAXPLAYERS];
7502
7503     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7504         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7505     names[0] = p = strdup(appData.participants);
7506     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7507
7508     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7509
7510     while(result = appData.results[nr]) {
7511         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7512         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7513         wScore = bScore = 0;
7514         switch(result) {
7515           case '+': wScore = 2; break;
7516           case '-': bScore = 2; break;
7517           case '=': wScore = bScore = 1; break;
7518           case ' ':
7519           case '*': return strdup("busy"); // tourney not finished
7520         }
7521         score[w] += wScore;
7522         score[b] += bScore;
7523         games[w]++;
7524         games[b]++;
7525         nr++;
7526     }
7527     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7528     for(w=0; w<nPlayers; w++) {
7529         bScore = -1;
7530         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7531         ranking[w] = b; points[w] = bScore; score[b] = -2;
7532     }
7533     p = malloc(nPlayers*34+1);
7534     for(w=0; w<nPlayers && w<display; w++)
7535         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7536     free(names[0]);
7537     return p;
7538 }
7539
7540 void
7541 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7542 {       // count all piece types
7543         int p, f, r;
7544         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7545         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7546         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7547                 p = board[r][f];
7548                 pCnt[p]++;
7549                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7550                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7551                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7552                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7553                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7554                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7555         }
7556 }
7557
7558 int
7559 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7560 {
7561         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7562         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7563
7564         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7565         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7566         if(myPawns == 2 && nMine == 3) // KPP
7567             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7568         if(myPawns == 1 && nMine == 2) // KP
7569             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7570         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7571             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7572         if(myPawns) return FALSE;
7573         if(pCnt[WhiteRook+side])
7574             return pCnt[BlackRook-side] ||
7575                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7576                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7577                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7578         if(pCnt[WhiteCannon+side]) {
7579             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7580             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7581         }
7582         if(pCnt[WhiteKnight+side])
7583             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7584         return FALSE;
7585 }
7586
7587 int
7588 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7589 {
7590         VariantClass v = gameInfo.variant;
7591
7592         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7593         if(v == VariantShatranj) return TRUE; // always winnable through baring
7594         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7595         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7596
7597         if(v == VariantXiangqi) {
7598                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7599
7600                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7601                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7602                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7603                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7604                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7605                 if(stale) // we have at least one last-rank P plus perhaps C
7606                     return majors // KPKX
7607                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7608                 else // KCA*E*
7609                     return pCnt[WhiteFerz+side] // KCAK
7610                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7611                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7612                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7613
7614         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7615                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7616
7617                 if(nMine == 1) return FALSE; // bare King
7618                 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
7619                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7620                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7621                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7622                 if(pCnt[WhiteKnight+side])
7623                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7624                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7625                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7626                 if(nBishops)
7627                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7628                 if(pCnt[WhiteAlfil+side])
7629                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7630                 if(pCnt[WhiteWazir+side])
7631                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7632         }
7633
7634         return TRUE;
7635 }
7636
7637 int
7638 CompareWithRights (Board b1, Board b2)
7639 {
7640     int rights = 0;
7641     if(!CompareBoards(b1, b2)) return FALSE;
7642     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7643     /* compare castling rights */
7644     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7645            rights++; /* King lost rights, while rook still had them */
7646     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7647         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7648            rights++; /* but at least one rook lost them */
7649     }
7650     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7651            rights++;
7652     if( b1[CASTLING][5] != NoRights ) {
7653         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7654            rights++;
7655     }
7656     return rights == 0;
7657 }
7658
7659 int
7660 Adjudicate (ChessProgramState *cps)
7661 {       // [HGM] some adjudications useful with buggy engines
7662         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7663         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7664         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7665         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7666         int k, count = 0; static int bare = 1;
7667         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7668         Boolean canAdjudicate = !appData.icsActive;
7669
7670         // most tests only when we understand the game, i.e. legality-checking on
7671             if( appData.testLegality )
7672             {   /* [HGM] Some more adjudications for obstinate engines */
7673                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7674                 static int moveCount = 6;
7675                 ChessMove result;
7676                 char *reason = NULL;
7677
7678                 /* Count what is on board. */
7679                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7680
7681                 /* Some material-based adjudications that have to be made before stalemate test */
7682                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7683                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7684                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7685                      if(canAdjudicate && appData.checkMates) {
7686                          if(engineOpponent)
7687                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7688                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7689                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7690                          return 1;
7691                      }
7692                 }
7693
7694                 /* Bare King in Shatranj (loses) or Losers (wins) */
7695                 if( nrW == 1 || nrB == 1) {
7696                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7697                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7698                      if(canAdjudicate && appData.checkMates) {
7699                          if(engineOpponent)
7700                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7701                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7702                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7703                          return 1;
7704                      }
7705                   } else
7706                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7707                   {    /* bare King */
7708                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7709                         if(canAdjudicate && appData.checkMates) {
7710                             /* but only adjudicate if adjudication enabled */
7711                             if(engineOpponent)
7712                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7713                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7714                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7715                             return 1;
7716                         }
7717                   }
7718                 } else bare = 1;
7719
7720
7721             // don't wait for engine to announce game end if we can judge ourselves
7722             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7723               case MT_CHECK:
7724                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7725                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7726                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7727                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7728                             checkCnt++;
7729                         if(checkCnt >= 2) {
7730                             reason = "Xboard adjudication: 3rd check";
7731                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7732                             break;
7733                         }
7734                     }
7735                 }
7736               case MT_NONE:
7737               default:
7738                 break;
7739               case MT_STALEMATE:
7740               case MT_STAINMATE:
7741                 reason = "Xboard adjudication: Stalemate";
7742                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7743                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7744                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7745                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7746                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7747                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7748                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7749                                                                         EP_CHECKMATE : EP_WINS);
7750                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7751                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7752                 }
7753                 break;
7754               case MT_CHECKMATE:
7755                 reason = "Xboard adjudication: Checkmate";
7756                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7757                 break;
7758             }
7759
7760                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7761                     case EP_STALEMATE:
7762                         result = GameIsDrawn; break;
7763                     case EP_CHECKMATE:
7764                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7765                     case EP_WINS:
7766                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7767                     default:
7768                         result = EndOfFile;
7769                 }
7770                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7771                     if(engineOpponent)
7772                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7773                     GameEnds( result, reason, GE_XBOARD );
7774                     return 1;
7775                 }
7776
7777                 /* Next absolutely insufficient mating material. */
7778                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7779                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7780                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7781
7782                      /* always flag draws, for judging claims */
7783                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7784
7785                      if(canAdjudicate && appData.materialDraws) {
7786                          /* but only adjudicate them if adjudication enabled */
7787                          if(engineOpponent) {
7788                            SendToProgram("force\n", engineOpponent); // suppress reply
7789                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7790                          }
7791                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7792                          return 1;
7793                      }
7794                 }
7795
7796                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7797                 if(gameInfo.variant == VariantXiangqi ?
7798                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7799                  : nrW + nrB == 4 &&
7800                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7801                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7802                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7803                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7804                    ) ) {
7805                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7806                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7807                           if(engineOpponent) {
7808                             SendToProgram("force\n", engineOpponent); // suppress reply
7809                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7810                           }
7811                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7812                           return 1;
7813                      }
7814                 } else moveCount = 6;
7815             }
7816
7817         // Repetition draws and 50-move rule can be applied independently of legality testing
7818
7819                 /* Check for rep-draws */
7820                 count = 0;
7821                 for(k = forwardMostMove-2;
7822                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7823                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7824                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7825                     k-=2)
7826                 {   int rights=0;
7827                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7828                         /* compare castling rights */
7829                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7830                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7831                                 rights++; /* King lost rights, while rook still had them */
7832                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7833                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7834                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7835                                    rights++; /* but at least one rook lost them */
7836                         }
7837                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7838                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7839                                 rights++;
7840                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7841                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7842                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7843                                    rights++;
7844                         }
7845                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7846                             && appData.drawRepeats > 1) {
7847                              /* adjudicate after user-specified nr of repeats */
7848                              int result = GameIsDrawn;
7849                              char *details = "XBoard adjudication: repetition draw";
7850                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7851                                 // [HGM] xiangqi: check for forbidden perpetuals
7852                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7853                                 for(m=forwardMostMove; m>k; m-=2) {
7854                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7855                                         ourPerpetual = 0; // the current mover did not always check
7856                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7857                                         hisPerpetual = 0; // the opponent did not always check
7858                                 }
7859                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7860                                                                         ourPerpetual, hisPerpetual);
7861                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7862                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7863                                     details = "Xboard adjudication: perpetual checking";
7864                                 } else
7865                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7866                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7867                                 } else
7868                                 // Now check for perpetual chases
7869                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7870                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7871                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7872                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7873                                         static char resdet[MSG_SIZ];
7874                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7875                                         details = resdet;
7876                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7877                                     } else
7878                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7879                                         break; // Abort repetition-checking loop.
7880                                 }
7881                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7882                              }
7883                              if(engineOpponent) {
7884                                SendToProgram("force\n", engineOpponent); // suppress reply
7885                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7886                              }
7887                              GameEnds( result, details, GE_XBOARD );
7888                              return 1;
7889                         }
7890                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7891                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7892                     }
7893                 }
7894
7895                 /* Now we test for 50-move draws. Determine ply count */
7896                 count = forwardMostMove;
7897                 /* look for last irreversble move */
7898                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7899                     count--;
7900                 /* if we hit starting position, add initial plies */
7901                 if( count == backwardMostMove )
7902                     count -= initialRulePlies;
7903                 count = forwardMostMove - count;
7904                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7905                         // adjust reversible move counter for checks in Xiangqi
7906                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7907                         if(i < backwardMostMove) i = backwardMostMove;
7908                         while(i <= forwardMostMove) {
7909                                 lastCheck = inCheck; // check evasion does not count
7910                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7911                                 if(inCheck || lastCheck) count--; // check does not count
7912                                 i++;
7913                         }
7914                 }
7915                 if( count >= 100)
7916                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7917                          /* this is used to judge if draw claims are legal */
7918                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7919                          if(engineOpponent) {
7920                            SendToProgram("force\n", engineOpponent); // suppress reply
7921                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7922                          }
7923                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7924                          return 1;
7925                 }
7926
7927                 /* if draw offer is pending, treat it as a draw claim
7928                  * when draw condition present, to allow engines a way to
7929                  * claim draws before making their move to avoid a race
7930                  * condition occurring after their move
7931                  */
7932                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7933                          char *p = NULL;
7934                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7935                              p = "Draw claim: 50-move rule";
7936                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7937                              p = "Draw claim: 3-fold repetition";
7938                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7939                              p = "Draw claim: insufficient mating material";
7940                          if( p != NULL && canAdjudicate) {
7941                              if(engineOpponent) {
7942                                SendToProgram("force\n", engineOpponent); // suppress reply
7943                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7944                              }
7945                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7946                              return 1;
7947                          }
7948                 }
7949
7950                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7951                     if(engineOpponent) {
7952                       SendToProgram("force\n", engineOpponent); // suppress reply
7953                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7954                     }
7955                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7956                     return 1;
7957                 }
7958         return 0;
7959 }
7960
7961 char *
7962 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7963 {   // [HGM] book: this routine intercepts moves to simulate book replies
7964     char *bookHit = NULL;
7965
7966     //first determine if the incoming move brings opponent into his book
7967     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7968         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7969     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7970     if(bookHit != NULL && !cps->bookSuspend) {
7971         // make sure opponent is not going to reply after receiving move to book position
7972         SendToProgram("force\n", cps);
7973         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7974     }
7975     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7976     // now arrange restart after book miss
7977     if(bookHit) {
7978         // after a book hit we never send 'go', and the code after the call to this routine
7979         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7980         char buf[MSG_SIZ], *move = bookHit;
7981         if(cps->useSAN) {
7982             int fromX, fromY, toX, toY;
7983             char promoChar;
7984             ChessMove moveType;
7985             move = buf + 30;
7986             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7987                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7988                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7989                                     PosFlags(forwardMostMove),
7990                                     fromY, fromX, toY, toX, promoChar, move);
7991             } else {
7992                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7993                 bookHit = NULL;
7994             }
7995         }
7996         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7997         SendToProgram(buf, cps);
7998         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7999     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8000         SendToProgram("go\n", cps);
8001         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8002     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8003         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8004             SendToProgram("go\n", cps);
8005         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8006     }
8007     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8008 }
8009
8010 int
8011 LoadError (char *errmess, ChessProgramState *cps)
8012 {   // unloads engine and switches back to -ncp mode if it was first
8013     if(cps->initDone) return FALSE;
8014     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8015     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8016     cps->pr = NoProc; 
8017     if(cps == &first) {
8018         appData.noChessProgram = TRUE;
8019         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8020         gameMode = BeginningOfGame; ModeHighlight();
8021         SetNCPMode();
8022     }
8023     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8024     DisplayMessage("", ""); // erase waiting message
8025     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8026     return TRUE;
8027 }
8028
8029 char *savedMessage;
8030 ChessProgramState *savedState;
8031 void
8032 DeferredBookMove (void)
8033 {
8034         if(savedState->lastPing != savedState->lastPong)
8035                     ScheduleDelayedEvent(DeferredBookMove, 10);
8036         else
8037         HandleMachineMove(savedMessage, savedState);
8038 }
8039
8040 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8041 static ChessProgramState *stalledEngine;
8042 static char stashedInputMove[MSG_SIZ];
8043
8044 void
8045 HandleMachineMove (char *message, ChessProgramState *cps)
8046 {
8047     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8048     char realname[MSG_SIZ];
8049     int fromX, fromY, toX, toY;
8050     ChessMove moveType;
8051     char promoChar;
8052     char *p, *pv=buf1;
8053     int machineWhite, oldError;
8054     char *bookHit;
8055
8056     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8057         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8058         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8059             DisplayError(_("Invalid pairing from pairing engine"), 0);
8060             return;
8061         }
8062         pairingReceived = 1;
8063         NextMatchGame();
8064         return; // Skim the pairing messages here.
8065     }
8066
8067     oldError = cps->userError; cps->userError = 0;
8068
8069 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8070     /*
8071      * Kludge to ignore BEL characters
8072      */
8073     while (*message == '\007') message++;
8074
8075     /*
8076      * [HGM] engine debug message: ignore lines starting with '#' character
8077      */
8078     if(cps->debug && *message == '#') return;
8079
8080     /*
8081      * Look for book output
8082      */
8083     if (cps == &first && bookRequested) {
8084         if (message[0] == '\t' || message[0] == ' ') {
8085             /* Part of the book output is here; append it */
8086             strcat(bookOutput, message);
8087             strcat(bookOutput, "  \n");
8088             return;
8089         } else if (bookOutput[0] != NULLCHAR) {
8090             /* All of book output has arrived; display it */
8091             char *p = bookOutput;
8092             while (*p != NULLCHAR) {
8093                 if (*p == '\t') *p = ' ';
8094                 p++;
8095             }
8096             DisplayInformation(bookOutput);
8097             bookRequested = FALSE;
8098             /* Fall through to parse the current output */
8099         }
8100     }
8101
8102     /*
8103      * Look for machine move.
8104      */
8105     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8106         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8107     {
8108         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8109             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8110             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8111             stalledEngine = cps;
8112             if(appData.ponderNextMove) { // bring both engines out of ponder
8113                 SendToProgram("easy\n", &first);
8114                 if(gameMode == TwoMachinesPlay) SendToProgram("easy\n", &second);
8115             }
8116             StopClocks();
8117             return;
8118         }
8119
8120         /* This method is only useful on engines that support ping */
8121         if (cps->lastPing != cps->lastPong) {
8122           if (gameMode == BeginningOfGame) {
8123             /* Extra move from before last new; ignore */
8124             if (appData.debugMode) {
8125                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8126             }
8127           } else {
8128             if (appData.debugMode) {
8129                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8130                         cps->which, gameMode);
8131             }
8132
8133             SendToProgram("undo\n", cps);
8134           }
8135           return;
8136         }
8137
8138         switch (gameMode) {
8139           case BeginningOfGame:
8140             /* Extra move from before last reset; ignore */
8141             if (appData.debugMode) {
8142                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8143             }
8144             return;
8145
8146           case EndOfGame:
8147           case IcsIdle:
8148           default:
8149             /* Extra move after we tried to stop.  The mode test is
8150                not a reliable way of detecting this problem, but it's
8151                the best we can do on engines that don't support ping.
8152             */
8153             if (appData.debugMode) {
8154                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8155                         cps->which, gameMode);
8156             }
8157             SendToProgram("undo\n", cps);
8158             return;
8159
8160           case MachinePlaysWhite:
8161           case IcsPlayingWhite:
8162             machineWhite = TRUE;
8163             break;
8164
8165           case MachinePlaysBlack:
8166           case IcsPlayingBlack:
8167             machineWhite = FALSE;
8168             break;
8169
8170           case TwoMachinesPlay:
8171             machineWhite = (cps->twoMachinesColor[0] == 'w');
8172             break;
8173         }
8174         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8175             if (appData.debugMode) {
8176                 fprintf(debugFP,
8177                         "Ignoring move out of turn by %s, gameMode %d"
8178                         ", forwardMost %d\n",
8179                         cps->which, gameMode, forwardMostMove);
8180             }
8181             return;
8182         }
8183
8184         if(cps->alphaRank) AlphaRank(machineMove, 4);
8185         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8186                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8187             /* Machine move could not be parsed; ignore it. */
8188           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8189                     machineMove, _(cps->which));
8190             DisplayError(buf1, 0);
8191             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8192                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8193             if (gameMode == TwoMachinesPlay) {
8194               GameEnds(machineWhite ? BlackWins : WhiteWins,
8195                        buf1, GE_XBOARD);
8196             }
8197             return;
8198         }
8199
8200         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8201         /* So we have to redo legality test with true e.p. status here,  */
8202         /* to make sure an illegal e.p. capture does not slip through,   */
8203         /* to cause a forfeit on a justified illegal-move complaint      */
8204         /* of the opponent.                                              */
8205         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8206            ChessMove moveType;
8207            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8208                              fromY, fromX, toY, toX, promoChar);
8209             if(moveType == IllegalMove) {
8210               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8211                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8212                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8213                            buf1, GE_XBOARD);
8214                 return;
8215            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8216            /* [HGM] Kludge to handle engines that send FRC-style castling
8217               when they shouldn't (like TSCP-Gothic) */
8218            switch(moveType) {
8219              case WhiteASideCastleFR:
8220              case BlackASideCastleFR:
8221                toX+=2;
8222                currentMoveString[2]++;
8223                break;
8224              case WhiteHSideCastleFR:
8225              case BlackHSideCastleFR:
8226                toX--;
8227                currentMoveString[2]--;
8228                break;
8229              default: ; // nothing to do, but suppresses warning of pedantic compilers
8230            }
8231         }
8232         hintRequested = FALSE;
8233         lastHint[0] = NULLCHAR;
8234         bookRequested = FALSE;
8235         /* Program may be pondering now */
8236         cps->maybeThinking = TRUE;
8237         if (cps->sendTime == 2) cps->sendTime = 1;
8238         if (cps->offeredDraw) cps->offeredDraw--;
8239
8240         /* [AS] Save move info*/
8241         pvInfoList[ forwardMostMove ].score = programStats.score;
8242         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8243         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8244
8245         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8246
8247         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8248         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8249             int count = 0;
8250
8251             while( count < adjudicateLossPlies ) {
8252                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8253
8254                 if( count & 1 ) {
8255                     score = -score; /* Flip score for winning side */
8256                 }
8257
8258                 if( score > adjudicateLossThreshold ) {
8259                     break;
8260                 }
8261
8262                 count++;
8263             }
8264
8265             if( count >= adjudicateLossPlies ) {
8266                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8267
8268                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8269                     "Xboard adjudication",
8270                     GE_XBOARD );
8271
8272                 return;
8273             }
8274         }
8275
8276         if(Adjudicate(cps)) {
8277             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8278             return; // [HGM] adjudicate: for all automatic game ends
8279         }
8280
8281 #if ZIPPY
8282         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8283             first.initDone) {
8284           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8285                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8286                 SendToICS("draw ");
8287                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8288           }
8289           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8290           ics_user_moved = 1;
8291           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8292                 char buf[3*MSG_SIZ];
8293
8294                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8295                         programStats.score / 100.,
8296                         programStats.depth,
8297                         programStats.time / 100.,
8298                         (unsigned int)programStats.nodes,
8299                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8300                         programStats.movelist);
8301                 SendToICS(buf);
8302 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8303           }
8304         }
8305 #endif
8306
8307         /* [AS] Clear stats for next move */
8308         ClearProgramStats();
8309         thinkOutput[0] = NULLCHAR;
8310         hiddenThinkOutputState = 0;
8311
8312         bookHit = NULL;
8313         if (gameMode == TwoMachinesPlay) {
8314             /* [HGM] relaying draw offers moved to after reception of move */
8315             /* and interpreting offer as claim if it brings draw condition */
8316             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8317                 SendToProgram("draw\n", cps->other);
8318             }
8319             if (cps->other->sendTime) {
8320                 SendTimeRemaining(cps->other,
8321                                   cps->other->twoMachinesColor[0] == 'w');
8322             }
8323             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8324             if (firstMove && !bookHit) {
8325                 firstMove = FALSE;
8326                 if (cps->other->useColors) {
8327                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8328                 }
8329                 SendToProgram("go\n", cps->other);
8330             }
8331             cps->other->maybeThinking = TRUE;
8332         }
8333
8334         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8335
8336         if (!pausing && appData.ringBellAfterMoves) {
8337             RingBell();
8338         }
8339
8340         /*
8341          * Reenable menu items that were disabled while
8342          * machine was thinking
8343          */
8344         if (gameMode != TwoMachinesPlay)
8345             SetUserThinkingEnables();
8346
8347         // [HGM] book: after book hit opponent has received move and is now in force mode
8348         // force the book reply into it, and then fake that it outputted this move by jumping
8349         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8350         if(bookHit) {
8351                 static char bookMove[MSG_SIZ]; // a bit generous?
8352
8353                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8354                 strcat(bookMove, bookHit);
8355                 message = bookMove;
8356                 cps = cps->other;
8357                 programStats.nodes = programStats.depth = programStats.time =
8358                 programStats.score = programStats.got_only_move = 0;
8359                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8360
8361                 if(cps->lastPing != cps->lastPong) {
8362                     savedMessage = message; // args for deferred call
8363                     savedState = cps;
8364                     ScheduleDelayedEvent(DeferredBookMove, 10);
8365                     return;
8366                 }
8367                 goto FakeBookMove;
8368         }
8369
8370         return;
8371     }
8372
8373     /* Set special modes for chess engines.  Later something general
8374      *  could be added here; for now there is just one kludge feature,
8375      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8376      *  when "xboard" is given as an interactive command.
8377      */
8378     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8379         cps->useSigint = FALSE;
8380         cps->useSigterm = FALSE;
8381     }
8382     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8383       ParseFeatures(message+8, cps);
8384       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8385     }
8386
8387     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8388                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8389       int dummy, s=6; char buf[MSG_SIZ];
8390       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8391       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8392       if(startedFromSetupPosition) return;
8393       ParseFEN(boards[0], &dummy, message+s);
8394       DrawPosition(TRUE, boards[0]);
8395       startedFromSetupPosition = TRUE;
8396       return;
8397     }
8398     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8399      * want this, I was asked to put it in, and obliged.
8400      */
8401     if (!strncmp(message, "setboard ", 9)) {
8402         Board initial_position;
8403
8404         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8405
8406         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8407             DisplayError(_("Bad FEN received from engine"), 0);
8408             return ;
8409         } else {
8410            Reset(TRUE, FALSE);
8411            CopyBoard(boards[0], initial_position);
8412            initialRulePlies = FENrulePlies;
8413            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8414            else gameMode = MachinePlaysBlack;
8415            DrawPosition(FALSE, boards[currentMove]);
8416         }
8417         return;
8418     }
8419
8420     /*
8421      * Look for communication commands
8422      */
8423     if (!strncmp(message, "telluser ", 9)) {
8424         if(message[9] == '\\' && message[10] == '\\')
8425             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8426         PlayTellSound();
8427         DisplayNote(message + 9);
8428         return;
8429     }
8430     if (!strncmp(message, "tellusererror ", 14)) {
8431         cps->userError = 1;
8432         if(message[14] == '\\' && message[15] == '\\')
8433             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8434         PlayTellSound();
8435         DisplayError(message + 14, 0);
8436         return;
8437     }
8438     if (!strncmp(message, "tellopponent ", 13)) {
8439       if (appData.icsActive) {
8440         if (loggedOn) {
8441           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8442           SendToICS(buf1);
8443         }
8444       } else {
8445         DisplayNote(message + 13);
8446       }
8447       return;
8448     }
8449     if (!strncmp(message, "tellothers ", 11)) {
8450       if (appData.icsActive) {
8451         if (loggedOn) {
8452           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8453           SendToICS(buf1);
8454         }
8455       }
8456       return;
8457     }
8458     if (!strncmp(message, "tellall ", 8)) {
8459       if (appData.icsActive) {
8460         if (loggedOn) {
8461           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8462           SendToICS(buf1);
8463         }
8464       } else {
8465         DisplayNote(message + 8);
8466       }
8467       return;
8468     }
8469     if (strncmp(message, "warning", 7) == 0) {
8470         /* Undocumented feature, use tellusererror in new code */
8471         DisplayError(message, 0);
8472         return;
8473     }
8474     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8475         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8476         strcat(realname, " query");
8477         AskQuestion(realname, buf2, buf1, cps->pr);
8478         return;
8479     }
8480     /* Commands from the engine directly to ICS.  We don't allow these to be
8481      *  sent until we are logged on. Crafty kibitzes have been known to
8482      *  interfere with the login process.
8483      */
8484     if (loggedOn) {
8485         if (!strncmp(message, "tellics ", 8)) {
8486             SendToICS(message + 8);
8487             SendToICS("\n");
8488             return;
8489         }
8490         if (!strncmp(message, "tellicsnoalias ", 15)) {
8491             SendToICS(ics_prefix);
8492             SendToICS(message + 15);
8493             SendToICS("\n");
8494             return;
8495         }
8496         /* The following are for backward compatibility only */
8497         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8498             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8499             SendToICS(ics_prefix);
8500             SendToICS(message);
8501             SendToICS("\n");
8502             return;
8503         }
8504     }
8505     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8506         return;
8507     }
8508     /*
8509      * If the move is illegal, cancel it and redraw the board.
8510      * Also deal with other error cases.  Matching is rather loose
8511      * here to accommodate engines written before the spec.
8512      */
8513     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8514         strncmp(message, "Error", 5) == 0) {
8515         if (StrStr(message, "name") ||
8516             StrStr(message, "rating") || StrStr(message, "?") ||
8517             StrStr(message, "result") || StrStr(message, "board") ||
8518             StrStr(message, "bk") || StrStr(message, "computer") ||
8519             StrStr(message, "variant") || StrStr(message, "hint") ||
8520             StrStr(message, "random") || StrStr(message, "depth") ||
8521             StrStr(message, "accepted")) {
8522             return;
8523         }
8524         if (StrStr(message, "protover")) {
8525           /* Program is responding to input, so it's apparently done
8526              initializing, and this error message indicates it is
8527              protocol version 1.  So we don't need to wait any longer
8528              for it to initialize and send feature commands. */
8529           FeatureDone(cps, 1);
8530           cps->protocolVersion = 1;
8531           return;
8532         }
8533         cps->maybeThinking = FALSE;
8534
8535         if (StrStr(message, "draw")) {
8536             /* Program doesn't have "draw" command */
8537             cps->sendDrawOffers = 0;
8538             return;
8539         }
8540         if (cps->sendTime != 1 &&
8541             (StrStr(message, "time") || StrStr(message, "otim"))) {
8542           /* Program apparently doesn't have "time" or "otim" command */
8543           cps->sendTime = 0;
8544           return;
8545         }
8546         if (StrStr(message, "analyze")) {
8547             cps->analysisSupport = FALSE;
8548             cps->analyzing = FALSE;
8549 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8550             EditGameEvent(); // [HGM] try to preserve loaded game
8551             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8552             DisplayError(buf2, 0);
8553             return;
8554         }
8555         if (StrStr(message, "(no matching move)st")) {
8556           /* Special kludge for GNU Chess 4 only */
8557           cps->stKludge = TRUE;
8558           SendTimeControl(cps, movesPerSession, timeControl,
8559                           timeIncrement, appData.searchDepth,
8560                           searchTime);
8561           return;
8562         }
8563         if (StrStr(message, "(no matching move)sd")) {
8564           /* Special kludge for GNU Chess 4 only */
8565           cps->sdKludge = TRUE;
8566           SendTimeControl(cps, movesPerSession, timeControl,
8567                           timeIncrement, appData.searchDepth,
8568                           searchTime);
8569           return;
8570         }
8571         if (!StrStr(message, "llegal")) {
8572             return;
8573         }
8574         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8575             gameMode == IcsIdle) return;
8576         if (forwardMostMove <= backwardMostMove) return;
8577         if (pausing) PauseEvent();
8578       if(appData.forceIllegal) {
8579             // [HGM] illegal: machine refused move; force position after move into it
8580           SendToProgram("force\n", cps);
8581           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8582                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8583                 // when black is to move, while there might be nothing on a2 or black
8584                 // might already have the move. So send the board as if white has the move.
8585                 // But first we must change the stm of the engine, as it refused the last move
8586                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8587                 if(WhiteOnMove(forwardMostMove)) {
8588                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8589                     SendBoard(cps, forwardMostMove); // kludgeless board
8590                 } else {
8591                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8592                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8593                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8594                 }
8595           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8596             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8597                  gameMode == TwoMachinesPlay)
8598               SendToProgram("go\n", cps);
8599             return;
8600       } else
8601         if (gameMode == PlayFromGameFile) {
8602             /* Stop reading this game file */
8603             gameMode = EditGame;
8604             ModeHighlight();
8605         }
8606         /* [HGM] illegal-move claim should forfeit game when Xboard */
8607         /* only passes fully legal moves                            */
8608         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8609             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8610                                 "False illegal-move claim", GE_XBOARD );
8611             return; // do not take back move we tested as valid
8612         }
8613         currentMove = forwardMostMove-1;
8614         DisplayMove(currentMove-1); /* before DisplayMoveError */
8615         SwitchClocks(forwardMostMove-1); // [HGM] race
8616         DisplayBothClocks();
8617         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8618                 parseList[currentMove], _(cps->which));
8619         DisplayMoveError(buf1);
8620         DrawPosition(FALSE, boards[currentMove]);
8621
8622         SetUserThinkingEnables();
8623         return;
8624     }
8625     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8626         /* Program has a broken "time" command that
8627            outputs a string not ending in newline.
8628            Don't use it. */
8629         cps->sendTime = 0;
8630     }
8631
8632     /*
8633      * If chess program startup fails, exit with an error message.
8634      * Attempts to recover here are futile. [HGM] Well, we try anyway
8635      */
8636     if ((StrStr(message, "unknown host") != NULL)
8637         || (StrStr(message, "No remote directory") != NULL)
8638         || (StrStr(message, "not found") != NULL)
8639         || (StrStr(message, "No such file") != NULL)
8640         || (StrStr(message, "can't alloc") != NULL)
8641         || (StrStr(message, "Permission denied") != NULL)) {
8642
8643         cps->maybeThinking = FALSE;
8644         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8645                 _(cps->which), cps->program, cps->host, message);
8646         RemoveInputSource(cps->isr);
8647         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8648             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8649             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8650         }
8651         return;
8652     }
8653
8654     /*
8655      * Look for hint output
8656      */
8657     if (sscanf(message, "Hint: %s", buf1) == 1) {
8658         if (cps == &first && hintRequested) {
8659             hintRequested = FALSE;
8660             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8661                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8662                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8663                                     PosFlags(forwardMostMove),
8664                                     fromY, fromX, toY, toX, promoChar, buf1);
8665                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8666                 DisplayInformation(buf2);
8667             } else {
8668                 /* Hint move could not be parsed!? */
8669               snprintf(buf2, sizeof(buf2),
8670                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8671                         buf1, _(cps->which));
8672                 DisplayError(buf2, 0);
8673             }
8674         } else {
8675           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8676         }
8677         return;
8678     }
8679
8680     /*
8681      * Ignore other messages if game is not in progress
8682      */
8683     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8684         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8685
8686     /*
8687      * look for win, lose, draw, or draw offer
8688      */
8689     if (strncmp(message, "1-0", 3) == 0) {
8690         char *p, *q, *r = "";
8691         p = strchr(message, '{');
8692         if (p) {
8693             q = strchr(p, '}');
8694             if (q) {
8695                 *q = NULLCHAR;
8696                 r = p + 1;
8697             }
8698         }
8699         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8700         return;
8701     } else if (strncmp(message, "0-1", 3) == 0) {
8702         char *p, *q, *r = "";
8703         p = strchr(message, '{');
8704         if (p) {
8705             q = strchr(p, '}');
8706             if (q) {
8707                 *q = NULLCHAR;
8708                 r = p + 1;
8709             }
8710         }
8711         /* Kludge for Arasan 4.1 bug */
8712         if (strcmp(r, "Black resigns") == 0) {
8713             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8714             return;
8715         }
8716         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8717         return;
8718     } else if (strncmp(message, "1/2", 3) == 0) {
8719         char *p, *q, *r = "";
8720         p = strchr(message, '{');
8721         if (p) {
8722             q = strchr(p, '}');
8723             if (q) {
8724                 *q = NULLCHAR;
8725                 r = p + 1;
8726             }
8727         }
8728
8729         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8730         return;
8731
8732     } else if (strncmp(message, "White resign", 12) == 0) {
8733         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8734         return;
8735     } else if (strncmp(message, "Black resign", 12) == 0) {
8736         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8737         return;
8738     } else if (strncmp(message, "White matches", 13) == 0 ||
8739                strncmp(message, "Black matches", 13) == 0   ) {
8740         /* [HGM] ignore GNUShogi noises */
8741         return;
8742     } else if (strncmp(message, "White", 5) == 0 &&
8743                message[5] != '(' &&
8744                StrStr(message, "Black") == NULL) {
8745         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8746         return;
8747     } else if (strncmp(message, "Black", 5) == 0 &&
8748                message[5] != '(') {
8749         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8750         return;
8751     } else if (strcmp(message, "resign") == 0 ||
8752                strcmp(message, "computer resigns") == 0) {
8753         switch (gameMode) {
8754           case MachinePlaysBlack:
8755           case IcsPlayingBlack:
8756             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8757             break;
8758           case MachinePlaysWhite:
8759           case IcsPlayingWhite:
8760             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8761             break;
8762           case TwoMachinesPlay:
8763             if (cps->twoMachinesColor[0] == 'w')
8764               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8765             else
8766               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8767             break;
8768           default:
8769             /* can't happen */
8770             break;
8771         }
8772         return;
8773     } else if (strncmp(message, "opponent mates", 14) == 0) {
8774         switch (gameMode) {
8775           case MachinePlaysBlack:
8776           case IcsPlayingBlack:
8777             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8778             break;
8779           case MachinePlaysWhite:
8780           case IcsPlayingWhite:
8781             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8782             break;
8783           case TwoMachinesPlay:
8784             if (cps->twoMachinesColor[0] == 'w')
8785               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8786             else
8787               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8788             break;
8789           default:
8790             /* can't happen */
8791             break;
8792         }
8793         return;
8794     } else if (strncmp(message, "computer mates", 14) == 0) {
8795         switch (gameMode) {
8796           case MachinePlaysBlack:
8797           case IcsPlayingBlack:
8798             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8799             break;
8800           case MachinePlaysWhite:
8801           case IcsPlayingWhite:
8802             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8803             break;
8804           case TwoMachinesPlay:
8805             if (cps->twoMachinesColor[0] == 'w')
8806               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8807             else
8808               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8809             break;
8810           default:
8811             /* can't happen */
8812             break;
8813         }
8814         return;
8815     } else if (strncmp(message, "checkmate", 9) == 0) {
8816         if (WhiteOnMove(forwardMostMove)) {
8817             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8818         } else {
8819             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8820         }
8821         return;
8822     } else if (strstr(message, "Draw") != NULL ||
8823                strstr(message, "game is a draw") != NULL) {
8824         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8825         return;
8826     } else if (strstr(message, "offer") != NULL &&
8827                strstr(message, "draw") != NULL) {
8828 #if ZIPPY
8829         if (appData.zippyPlay && first.initDone) {
8830             /* Relay offer to ICS */
8831             SendToICS(ics_prefix);
8832             SendToICS("draw\n");
8833         }
8834 #endif
8835         cps->offeredDraw = 2; /* valid until this engine moves twice */
8836         if (gameMode == TwoMachinesPlay) {
8837             if (cps->other->offeredDraw) {
8838                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8839             /* [HGM] in two-machine mode we delay relaying draw offer      */
8840             /* until after we also have move, to see if it is really claim */
8841             }
8842         } else if (gameMode == MachinePlaysWhite ||
8843                    gameMode == MachinePlaysBlack) {
8844           if (userOfferedDraw) {
8845             DisplayInformation(_("Machine accepts your draw offer"));
8846             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8847           } else {
8848             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8849           }
8850         }
8851     }
8852
8853
8854     /*
8855      * Look for thinking output
8856      */
8857     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8858           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8859                                 ) {
8860         int plylev, mvleft, mvtot, curscore, time;
8861         char mvname[MOVE_LEN];
8862         u64 nodes; // [DM]
8863         char plyext;
8864         int ignore = FALSE;
8865         int prefixHint = FALSE;
8866         mvname[0] = NULLCHAR;
8867
8868         switch (gameMode) {
8869           case MachinePlaysBlack:
8870           case IcsPlayingBlack:
8871             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8872             break;
8873           case MachinePlaysWhite:
8874           case IcsPlayingWhite:
8875             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8876             break;
8877           case AnalyzeMode:
8878           case AnalyzeFile:
8879             break;
8880           case IcsObserving: /* [DM] icsEngineAnalyze */
8881             if (!appData.icsEngineAnalyze) ignore = TRUE;
8882             break;
8883           case TwoMachinesPlay:
8884             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8885                 ignore = TRUE;
8886             }
8887             break;
8888           default:
8889             ignore = TRUE;
8890             break;
8891         }
8892
8893         if (!ignore) {
8894             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8895             buf1[0] = NULLCHAR;
8896             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8897                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8898
8899                 if (plyext != ' ' && plyext != '\t') {
8900                     time *= 100;
8901                 }
8902
8903                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8904                 if( cps->scoreIsAbsolute &&
8905                     ( gameMode == MachinePlaysBlack ||
8906                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8907                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8908                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8909                      !WhiteOnMove(currentMove)
8910                     ) )
8911                 {
8912                     curscore = -curscore;
8913                 }
8914
8915                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8916
8917                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8918                         char buf[MSG_SIZ];
8919                         FILE *f;
8920                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8921                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8922                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8923                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8924                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8925                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8926                                 fclose(f);
8927                         } else DisplayError(_("failed writing PV"), 0);
8928                 }
8929
8930                 tempStats.depth = plylev;
8931                 tempStats.nodes = nodes;
8932                 tempStats.time = time;
8933                 tempStats.score = curscore;
8934                 tempStats.got_only_move = 0;
8935
8936                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8937                         int ticklen;
8938
8939                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8940                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8941                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8942                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8943                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8944                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8945                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8946                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8947                 }
8948
8949                 /* Buffer overflow protection */
8950                 if (pv[0] != NULLCHAR) {
8951                     if (strlen(pv) >= sizeof(tempStats.movelist)
8952                         && appData.debugMode) {
8953                         fprintf(debugFP,
8954                                 "PV is too long; using the first %u bytes.\n",
8955                                 (unsigned) sizeof(tempStats.movelist) - 1);
8956                     }
8957
8958                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8959                 } else {
8960                     sprintf(tempStats.movelist, " no PV\n");
8961                 }
8962
8963                 if (tempStats.seen_stat) {
8964                     tempStats.ok_to_send = 1;
8965                 }
8966
8967                 if (strchr(tempStats.movelist, '(') != NULL) {
8968                     tempStats.line_is_book = 1;
8969                     tempStats.nr_moves = 0;
8970                     tempStats.moves_left = 0;
8971                 } else {
8972                     tempStats.line_is_book = 0;
8973                 }
8974
8975                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8976                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8977
8978                 SendProgramStatsToFrontend( cps, &tempStats );
8979
8980                 /*
8981                     [AS] Protect the thinkOutput buffer from overflow... this
8982                     is only useful if buf1 hasn't overflowed first!
8983                 */
8984                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8985                          plylev,
8986                          (gameMode == TwoMachinesPlay ?
8987                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8988                          ((double) curscore) / 100.0,
8989                          prefixHint ? lastHint : "",
8990                          prefixHint ? " " : "" );
8991
8992                 if( buf1[0] != NULLCHAR ) {
8993                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8994
8995                     if( strlen(pv) > max_len ) {
8996                         if( appData.debugMode) {
8997                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8998                         }
8999                         pv[max_len+1] = '\0';
9000                     }
9001
9002                     strcat( thinkOutput, pv);
9003                 }
9004
9005                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9006                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9007                     DisplayMove(currentMove - 1);
9008                 }
9009                 return;
9010
9011             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9012                 /* crafty (9.25+) says "(only move) <move>"
9013                  * if there is only 1 legal move
9014                  */
9015                 sscanf(p, "(only move) %s", buf1);
9016                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9017                 sprintf(programStats.movelist, "%s (only move)", buf1);
9018                 programStats.depth = 1;
9019                 programStats.nr_moves = 1;
9020                 programStats.moves_left = 1;
9021                 programStats.nodes = 1;
9022                 programStats.time = 1;
9023                 programStats.got_only_move = 1;
9024
9025                 /* Not really, but we also use this member to
9026                    mean "line isn't going to change" (Crafty
9027                    isn't searching, so stats won't change) */
9028                 programStats.line_is_book = 1;
9029
9030                 SendProgramStatsToFrontend( cps, &programStats );
9031
9032                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9033                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9034                     DisplayMove(currentMove - 1);
9035                 }
9036                 return;
9037             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9038                               &time, &nodes, &plylev, &mvleft,
9039                               &mvtot, mvname) >= 5) {
9040                 /* The stat01: line is from Crafty (9.29+) in response
9041                    to the "." command */
9042                 programStats.seen_stat = 1;
9043                 cps->maybeThinking = TRUE;
9044
9045                 if (programStats.got_only_move || !appData.periodicUpdates)
9046                   return;
9047
9048                 programStats.depth = plylev;
9049                 programStats.time = time;
9050                 programStats.nodes = nodes;
9051                 programStats.moves_left = mvleft;
9052                 programStats.nr_moves = mvtot;
9053                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9054                 programStats.ok_to_send = 1;
9055                 programStats.movelist[0] = '\0';
9056
9057                 SendProgramStatsToFrontend( cps, &programStats );
9058
9059                 return;
9060
9061             } else if (strncmp(message,"++",2) == 0) {
9062                 /* Crafty 9.29+ outputs this */
9063                 programStats.got_fail = 2;
9064                 return;
9065
9066             } else if (strncmp(message,"--",2) == 0) {
9067                 /* Crafty 9.29+ outputs this */
9068                 programStats.got_fail = 1;
9069                 return;
9070
9071             } else if (thinkOutput[0] != NULLCHAR &&
9072                        strncmp(message, "    ", 4) == 0) {
9073                 unsigned message_len;
9074
9075                 p = message;
9076                 while (*p && *p == ' ') p++;
9077
9078                 message_len = strlen( p );
9079
9080                 /* [AS] Avoid buffer overflow */
9081                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9082                     strcat(thinkOutput, " ");
9083                     strcat(thinkOutput, p);
9084                 }
9085
9086                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9087                     strcat(programStats.movelist, " ");
9088                     strcat(programStats.movelist, p);
9089                 }
9090
9091                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9092                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9093                     DisplayMove(currentMove - 1);
9094                 }
9095                 return;
9096             }
9097         }
9098         else {
9099             buf1[0] = NULLCHAR;
9100
9101             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9102                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9103             {
9104                 ChessProgramStats cpstats;
9105
9106                 if (plyext != ' ' && plyext != '\t') {
9107                     time *= 100;
9108                 }
9109
9110                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9111                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9112                     curscore = -curscore;
9113                 }
9114
9115                 cpstats.depth = plylev;
9116                 cpstats.nodes = nodes;
9117                 cpstats.time = time;
9118                 cpstats.score = curscore;
9119                 cpstats.got_only_move = 0;
9120                 cpstats.movelist[0] = '\0';
9121
9122                 if (buf1[0] != NULLCHAR) {
9123                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9124                 }
9125
9126                 cpstats.ok_to_send = 0;
9127                 cpstats.line_is_book = 0;
9128                 cpstats.nr_moves = 0;
9129                 cpstats.moves_left = 0;
9130
9131                 SendProgramStatsToFrontend( cps, &cpstats );
9132             }
9133         }
9134     }
9135 }
9136
9137
9138 /* Parse a game score from the character string "game", and
9139    record it as the history of the current game.  The game
9140    score is NOT assumed to start from the standard position.
9141    The display is not updated in any way.
9142    */
9143 void
9144 ParseGameHistory (char *game)
9145 {
9146     ChessMove moveType;
9147     int fromX, fromY, toX, toY, boardIndex;
9148     char promoChar;
9149     char *p, *q;
9150     char buf[MSG_SIZ];
9151
9152     if (appData.debugMode)
9153       fprintf(debugFP, "Parsing game history: %s\n", game);
9154
9155     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9156     gameInfo.site = StrSave(appData.icsHost);
9157     gameInfo.date = PGNDate();
9158     gameInfo.round = StrSave("-");
9159
9160     /* Parse out names of players */
9161     while (*game == ' ') game++;
9162     p = buf;
9163     while (*game != ' ') *p++ = *game++;
9164     *p = NULLCHAR;
9165     gameInfo.white = StrSave(buf);
9166     while (*game == ' ') game++;
9167     p = buf;
9168     while (*game != ' ' && *game != '\n') *p++ = *game++;
9169     *p = NULLCHAR;
9170     gameInfo.black = StrSave(buf);
9171
9172     /* Parse moves */
9173     boardIndex = blackPlaysFirst ? 1 : 0;
9174     yynewstr(game);
9175     for (;;) {
9176         yyboardindex = boardIndex;
9177         moveType = (ChessMove) Myylex();
9178         switch (moveType) {
9179           case IllegalMove:             /* maybe suicide chess, etc. */
9180   if (appData.debugMode) {
9181     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9182     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9183     setbuf(debugFP, NULL);
9184   }
9185           case WhitePromotion:
9186           case BlackPromotion:
9187           case WhiteNonPromotion:
9188           case BlackNonPromotion:
9189           case NormalMove:
9190           case WhiteCapturesEnPassant:
9191           case BlackCapturesEnPassant:
9192           case WhiteKingSideCastle:
9193           case WhiteQueenSideCastle:
9194           case BlackKingSideCastle:
9195           case BlackQueenSideCastle:
9196           case WhiteKingSideCastleWild:
9197           case WhiteQueenSideCastleWild:
9198           case BlackKingSideCastleWild:
9199           case BlackQueenSideCastleWild:
9200           /* PUSH Fabien */
9201           case WhiteHSideCastleFR:
9202           case WhiteASideCastleFR:
9203           case BlackHSideCastleFR:
9204           case BlackASideCastleFR:
9205           /* POP Fabien */
9206             fromX = currentMoveString[0] - AAA;
9207             fromY = currentMoveString[1] - ONE;
9208             toX = currentMoveString[2] - AAA;
9209             toY = currentMoveString[3] - ONE;
9210             promoChar = currentMoveString[4];
9211             break;
9212           case WhiteDrop:
9213           case BlackDrop:
9214             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9215             fromX = moveType == WhiteDrop ?
9216               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9217             (int) CharToPiece(ToLower(currentMoveString[0]));
9218             fromY = DROP_RANK;
9219             toX = currentMoveString[2] - AAA;
9220             toY = currentMoveString[3] - ONE;
9221             promoChar = NULLCHAR;
9222             break;
9223           case AmbiguousMove:
9224             /* bug? */
9225             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9226   if (appData.debugMode) {
9227     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9228     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9229     setbuf(debugFP, NULL);
9230   }
9231             DisplayError(buf, 0);
9232             return;
9233           case ImpossibleMove:
9234             /* bug? */
9235             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9236   if (appData.debugMode) {
9237     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9238     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9239     setbuf(debugFP, NULL);
9240   }
9241             DisplayError(buf, 0);
9242             return;
9243           case EndOfFile:
9244             if (boardIndex < backwardMostMove) {
9245                 /* Oops, gap.  How did that happen? */
9246                 DisplayError(_("Gap in move list"), 0);
9247                 return;
9248             }
9249             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9250             if (boardIndex > forwardMostMove) {
9251                 forwardMostMove = boardIndex;
9252             }
9253             return;
9254           case ElapsedTime:
9255             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9256                 strcat(parseList[boardIndex-1], " ");
9257                 strcat(parseList[boardIndex-1], yy_text);
9258             }
9259             continue;
9260           case Comment:
9261           case PGNTag:
9262           case NAG:
9263           default:
9264             /* ignore */
9265             continue;
9266           case WhiteWins:
9267           case BlackWins:
9268           case GameIsDrawn:
9269           case GameUnfinished:
9270             if (gameMode == IcsExamining) {
9271                 if (boardIndex < backwardMostMove) {
9272                     /* Oops, gap.  How did that happen? */
9273                     return;
9274                 }
9275                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9276                 return;
9277             }
9278             gameInfo.result = moveType;
9279             p = strchr(yy_text, '{');
9280             if (p == NULL) p = strchr(yy_text, '(');
9281             if (p == NULL) {
9282                 p = yy_text;
9283                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9284             } else {
9285                 q = strchr(p, *p == '{' ? '}' : ')');
9286                 if (q != NULL) *q = NULLCHAR;
9287                 p++;
9288             }
9289             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9290             gameInfo.resultDetails = StrSave(p);
9291             continue;
9292         }
9293         if (boardIndex >= forwardMostMove &&
9294             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9295             backwardMostMove = blackPlaysFirst ? 1 : 0;
9296             return;
9297         }
9298         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9299                                  fromY, fromX, toY, toX, promoChar,
9300                                  parseList[boardIndex]);
9301         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9302         /* currentMoveString is set as a side-effect of yylex */
9303         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9304         strcat(moveList[boardIndex], "\n");
9305         boardIndex++;
9306         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9307         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9308           case MT_NONE:
9309           case MT_STALEMATE:
9310           default:
9311             break;
9312           case MT_CHECK:
9313             if(gameInfo.variant != VariantShogi)
9314                 strcat(parseList[boardIndex - 1], "+");
9315             break;
9316           case MT_CHECKMATE:
9317           case MT_STAINMATE:
9318             strcat(parseList[boardIndex - 1], "#");
9319             break;
9320         }
9321     }
9322 }
9323
9324
9325 /* Apply a move to the given board  */
9326 void
9327 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9328 {
9329   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9330   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9331
9332     /* [HGM] compute & store e.p. status and castling rights for new position */
9333     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9334
9335       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9336       oldEP = (signed char)board[EP_STATUS];
9337       board[EP_STATUS] = EP_NONE;
9338
9339   if (fromY == DROP_RANK) {
9340         /* must be first */
9341         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9342             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9343             return;
9344         }
9345         piece = board[toY][toX] = (ChessSquare) fromX;
9346   } else {
9347       int i;
9348
9349       if( board[toY][toX] != EmptySquare )
9350            board[EP_STATUS] = EP_CAPTURE;
9351
9352       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9353            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9354                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9355       } else
9356       if( board[fromY][fromX] == WhitePawn ) {
9357            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9358                board[EP_STATUS] = EP_PAWN_MOVE;
9359            if( toY-fromY==2) {
9360                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9361                         gameInfo.variant != VariantBerolina || toX < fromX)
9362                       board[EP_STATUS] = toX | berolina;
9363                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9364                         gameInfo.variant != VariantBerolina || toX > fromX)
9365                       board[EP_STATUS] = toX;
9366            }
9367       } else
9368       if( board[fromY][fromX] == BlackPawn ) {
9369            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9370                board[EP_STATUS] = EP_PAWN_MOVE;
9371            if( toY-fromY== -2) {
9372                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9373                         gameInfo.variant != VariantBerolina || toX < fromX)
9374                       board[EP_STATUS] = toX | berolina;
9375                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9376                         gameInfo.variant != VariantBerolina || toX > fromX)
9377                       board[EP_STATUS] = toX;
9378            }
9379        }
9380
9381        for(i=0; i<nrCastlingRights; i++) {
9382            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9383               board[CASTLING][i] == toX   && castlingRank[i] == toY
9384              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9385        }
9386
9387        if(gameInfo.variant == VariantSChess) { // update virginity
9388            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9389            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9390            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9391            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9392        }
9393
9394      if (fromX == toX && fromY == toY) return;
9395
9396      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9397      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9398      if(gameInfo.variant == VariantKnightmate)
9399          king += (int) WhiteUnicorn - (int) WhiteKing;
9400
9401     /* Code added by Tord: */
9402     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9403     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9404         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9405       board[fromY][fromX] = EmptySquare;
9406       board[toY][toX] = EmptySquare;
9407       if((toX > fromX) != (piece == WhiteRook)) {
9408         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9409       } else {
9410         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9411       }
9412     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9413                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9414       board[fromY][fromX] = EmptySquare;
9415       board[toY][toX] = EmptySquare;
9416       if((toX > fromX) != (piece == BlackRook)) {
9417         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9418       } else {
9419         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9420       }
9421     /* End of code added by Tord */
9422
9423     } else if (board[fromY][fromX] == king
9424         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9425         && toY == fromY && toX > fromX+1) {
9426         board[fromY][fromX] = EmptySquare;
9427         board[toY][toX] = king;
9428         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9429         board[fromY][BOARD_RGHT-1] = EmptySquare;
9430     } else if (board[fromY][fromX] == king
9431         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9432                && toY == fromY && toX < fromX-1) {
9433         board[fromY][fromX] = EmptySquare;
9434         board[toY][toX] = king;
9435         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9436         board[fromY][BOARD_LEFT] = EmptySquare;
9437     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9438                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9439                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9440                ) {
9441         /* white pawn promotion */
9442         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9443         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
9444            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9445             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9446         board[fromY][fromX] = EmptySquare;
9447     } else if ((fromY >= BOARD_HEIGHT>>1)
9448                && (toX != fromX)
9449                && gameInfo.variant != VariantXiangqi
9450                && gameInfo.variant != VariantBerolina
9451                && (board[fromY][fromX] == WhitePawn)
9452                && (board[toY][toX] == EmptySquare)) {
9453         board[fromY][fromX] = EmptySquare;
9454         board[toY][toX] = WhitePawn;
9455         captured = board[toY - 1][toX];
9456         board[toY - 1][toX] = EmptySquare;
9457     } else if ((fromY == BOARD_HEIGHT-4)
9458                && (toX == fromX)
9459                && gameInfo.variant == VariantBerolina
9460                && (board[fromY][fromX] == WhitePawn)
9461                && (board[toY][toX] == EmptySquare)) {
9462         board[fromY][fromX] = EmptySquare;
9463         board[toY][toX] = WhitePawn;
9464         if(oldEP & EP_BEROLIN_A) {
9465                 captured = board[fromY][fromX-1];
9466                 board[fromY][fromX-1] = EmptySquare;
9467         }else{  captured = board[fromY][fromX+1];
9468                 board[fromY][fromX+1] = EmptySquare;
9469         }
9470     } else if (board[fromY][fromX] == king
9471         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9472                && toY == fromY && toX > fromX+1) {
9473         board[fromY][fromX] = EmptySquare;
9474         board[toY][toX] = king;
9475         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9476         board[fromY][BOARD_RGHT-1] = EmptySquare;
9477     } else if (board[fromY][fromX] == king
9478         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9479                && toY == fromY && toX < fromX-1) {
9480         board[fromY][fromX] = EmptySquare;
9481         board[toY][toX] = king;
9482         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9483         board[fromY][BOARD_LEFT] = EmptySquare;
9484     } else if (fromY == 7 && fromX == 3
9485                && board[fromY][fromX] == BlackKing
9486                && toY == 7 && toX == 5) {
9487         board[fromY][fromX] = EmptySquare;
9488         board[toY][toX] = BlackKing;
9489         board[fromY][7] = EmptySquare;
9490         board[toY][4] = BlackRook;
9491     } else if (fromY == 7 && fromX == 3
9492                && board[fromY][fromX] == BlackKing
9493                && toY == 7 && toX == 1) {
9494         board[fromY][fromX] = EmptySquare;
9495         board[toY][toX] = BlackKing;
9496         board[fromY][0] = EmptySquare;
9497         board[toY][2] = BlackRook;
9498     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9499                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9500                && toY < promoRank && promoChar
9501                ) {
9502         /* black pawn promotion */
9503         board[toY][toX] = CharToPiece(ToLower(promoChar));
9504         if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
9505            && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9506             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9507         board[fromY][fromX] = EmptySquare;
9508     } else if ((fromY < BOARD_HEIGHT>>1)
9509                && (toX != fromX)
9510                && gameInfo.variant != VariantXiangqi
9511                && gameInfo.variant != VariantBerolina
9512                && (board[fromY][fromX] == BlackPawn)
9513                && (board[toY][toX] == EmptySquare)) {
9514         board[fromY][fromX] = EmptySquare;
9515         board[toY][toX] = BlackPawn;
9516         captured = board[toY + 1][toX];
9517         board[toY + 1][toX] = EmptySquare;
9518     } else if ((fromY == 3)
9519                && (toX == fromX)
9520                && gameInfo.variant == VariantBerolina
9521                && (board[fromY][fromX] == BlackPawn)
9522                && (board[toY][toX] == EmptySquare)) {
9523         board[fromY][fromX] = EmptySquare;
9524         board[toY][toX] = BlackPawn;
9525         if(oldEP & EP_BEROLIN_A) {
9526                 captured = board[fromY][fromX-1];
9527                 board[fromY][fromX-1] = EmptySquare;
9528         }else{  captured = board[fromY][fromX+1];
9529                 board[fromY][fromX+1] = EmptySquare;
9530         }
9531     } else {
9532         board[toY][toX] = board[fromY][fromX];
9533         board[fromY][fromX] = EmptySquare;
9534     }
9535   }
9536
9537     if (gameInfo.holdingsWidth != 0) {
9538
9539       /* !!A lot more code needs to be written to support holdings  */
9540       /* [HGM] OK, so I have written it. Holdings are stored in the */
9541       /* penultimate board files, so they are automaticlly stored   */
9542       /* in the game history.                                       */
9543       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9544                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9545         /* Delete from holdings, by decreasing count */
9546         /* and erasing image if necessary            */
9547         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9548         if(p < (int) BlackPawn) { /* white drop */
9549              p -= (int)WhitePawn;
9550                  p = PieceToNumber((ChessSquare)p);
9551              if(p >= gameInfo.holdingsSize) p = 0;
9552              if(--board[p][BOARD_WIDTH-2] <= 0)
9553                   board[p][BOARD_WIDTH-1] = EmptySquare;
9554              if((int)board[p][BOARD_WIDTH-2] < 0)
9555                         board[p][BOARD_WIDTH-2] = 0;
9556         } else {                  /* black drop */
9557              p -= (int)BlackPawn;
9558                  p = PieceToNumber((ChessSquare)p);
9559              if(p >= gameInfo.holdingsSize) p = 0;
9560              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9561                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9562              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9563                         board[BOARD_HEIGHT-1-p][1] = 0;
9564         }
9565       }
9566       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9567           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9568         /* [HGM] holdings: Add to holdings, if holdings exist */
9569         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9570                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9571                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9572         }
9573         p = (int) captured;
9574         if (p >= (int) BlackPawn) {
9575           p -= (int)BlackPawn;
9576           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9577                   /* in Shogi restore piece to its original  first */
9578                   captured = (ChessSquare) (DEMOTED captured);
9579                   p = DEMOTED p;
9580           }
9581           p = PieceToNumber((ChessSquare)p);
9582           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9583           board[p][BOARD_WIDTH-2]++;
9584           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9585         } else {
9586           p -= (int)WhitePawn;
9587           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9588                   captured = (ChessSquare) (DEMOTED captured);
9589                   p = DEMOTED p;
9590           }
9591           p = PieceToNumber((ChessSquare)p);
9592           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9593           board[BOARD_HEIGHT-1-p][1]++;
9594           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9595         }
9596       }
9597     } else if (gameInfo.variant == VariantAtomic) {
9598       if (captured != EmptySquare) {
9599         int y, x;
9600         for (y = toY-1; y <= toY+1; y++) {
9601           for (x = toX-1; x <= toX+1; x++) {
9602             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9603                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9604               board[y][x] = EmptySquare;
9605             }
9606           }
9607         }
9608         board[toY][toX] = EmptySquare;
9609       }
9610     }
9611     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9612         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9613     } else
9614     if(promoChar == '+') {
9615         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9616         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9617     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9618         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9619         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9620            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9621         board[toY][toX] = newPiece;
9622     }
9623     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9624                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9625         // [HGM] superchess: take promotion piece out of holdings
9626         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9627         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9628             if(!--board[k][BOARD_WIDTH-2])
9629                 board[k][BOARD_WIDTH-1] = EmptySquare;
9630         } else {
9631             if(!--board[BOARD_HEIGHT-1-k][1])
9632                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9633         }
9634     }
9635
9636 }
9637
9638 /* Updates forwardMostMove */
9639 void
9640 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9641 {
9642 //    forwardMostMove++; // [HGM] bare: moved downstream
9643
9644     (void) CoordsToAlgebraic(boards[forwardMostMove],
9645                              PosFlags(forwardMostMove),
9646                              fromY, fromX, toY, toX, promoChar,
9647                              parseList[forwardMostMove]);
9648
9649     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9650         int timeLeft; static int lastLoadFlag=0; int king, piece;
9651         piece = boards[forwardMostMove][fromY][fromX];
9652         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9653         if(gameInfo.variant == VariantKnightmate)
9654             king += (int) WhiteUnicorn - (int) WhiteKing;
9655         if(forwardMostMove == 0) {
9656             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9657                 fprintf(serverMoves, "%s;", UserName());
9658             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9659                 fprintf(serverMoves, "%s;", second.tidy);
9660             fprintf(serverMoves, "%s;", first.tidy);
9661             if(gameMode == MachinePlaysWhite)
9662                 fprintf(serverMoves, "%s;", UserName());
9663             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9664                 fprintf(serverMoves, "%s;", second.tidy);
9665         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9666         lastLoadFlag = loadFlag;
9667         // print base move
9668         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9669         // print castling suffix
9670         if( toY == fromY && piece == king ) {
9671             if(toX-fromX > 1)
9672                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9673             if(fromX-toX >1)
9674                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9675         }
9676         // e.p. suffix
9677         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9678              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9679              boards[forwardMostMove][toY][toX] == EmptySquare
9680              && fromX != toX && fromY != toY)
9681                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9682         // promotion suffix
9683         if(promoChar != NULLCHAR) {
9684             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9685                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9686                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9687             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9688         }
9689         if(!loadFlag) {
9690                 char buf[MOVE_LEN*2], *p; int len;
9691             fprintf(serverMoves, "/%d/%d",
9692                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9693             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9694             else                      timeLeft = blackTimeRemaining/1000;
9695             fprintf(serverMoves, "/%d", timeLeft);
9696                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9697                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9698                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9699                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9700             fprintf(serverMoves, "/%s", buf);
9701         }
9702         fflush(serverMoves);
9703     }
9704
9705     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9706         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9707       return;
9708     }
9709     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9710     if (commentList[forwardMostMove+1] != NULL) {
9711         free(commentList[forwardMostMove+1]);
9712         commentList[forwardMostMove+1] = NULL;
9713     }
9714     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9715     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9716     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9717     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9718     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9719     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9720     adjustedClock = FALSE;
9721     gameInfo.result = GameUnfinished;
9722     if (gameInfo.resultDetails != NULL) {
9723         free(gameInfo.resultDetails);
9724         gameInfo.resultDetails = NULL;
9725     }
9726     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9727                               moveList[forwardMostMove - 1]);
9728     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9729       case MT_NONE:
9730       case MT_STALEMATE:
9731       default:
9732         break;
9733       case MT_CHECK:
9734         if(gameInfo.variant != VariantShogi)
9735             strcat(parseList[forwardMostMove - 1], "+");
9736         break;
9737       case MT_CHECKMATE:
9738       case MT_STAINMATE:
9739         strcat(parseList[forwardMostMove - 1], "#");
9740         break;
9741     }
9742
9743 }
9744
9745 /* Updates currentMove if not pausing */
9746 void
9747 ShowMove (int fromX, int fromY, int toX, int toY)
9748 {
9749     int instant = (gameMode == PlayFromGameFile) ?
9750         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9751     if(appData.noGUI) return;
9752     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9753         if (!instant) {
9754             if (forwardMostMove == currentMove + 1) {
9755                 AnimateMove(boards[forwardMostMove - 1],
9756                             fromX, fromY, toX, toY);
9757             }
9758         }
9759         currentMove = forwardMostMove;
9760     }
9761
9762     if (instant) return;
9763
9764     DisplayMove(currentMove - 1);
9765     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9766             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9767                 SetHighlights(fromX, fromY, toX, toY);
9768             }
9769     }
9770     DrawPosition(FALSE, boards[currentMove]);
9771     DisplayBothClocks();
9772     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9773 }
9774
9775 void
9776 SendEgtPath (ChessProgramState *cps)
9777 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9778         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9779
9780         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9781
9782         while(*p) {
9783             char c, *q = name+1, *r, *s;
9784
9785             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9786             while(*p && *p != ',') *q++ = *p++;
9787             *q++ = ':'; *q = 0;
9788             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9789                 strcmp(name, ",nalimov:") == 0 ) {
9790                 // take nalimov path from the menu-changeable option first, if it is defined
9791               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9792                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9793             } else
9794             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9795                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9796                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9797                 s = r = StrStr(s, ":") + 1; // beginning of path info
9798                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9799                 c = *r; *r = 0;             // temporarily null-terminate path info
9800                     *--q = 0;               // strip of trailig ':' from name
9801                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9802                 *r = c;
9803                 SendToProgram(buf,cps);     // send egtbpath command for this format
9804             }
9805             if(*p == ',') p++; // read away comma to position for next format name
9806         }
9807 }
9808
9809 void
9810 InitChessProgram (ChessProgramState *cps, int setup)
9811 /* setup needed to setup FRC opening position */
9812 {
9813     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9814     if (appData.noChessProgram) return;
9815     hintRequested = FALSE;
9816     bookRequested = FALSE;
9817
9818     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9819     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9820     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9821     if(cps->memSize) { /* [HGM] memory */
9822       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9823         SendToProgram(buf, cps);
9824     }
9825     SendEgtPath(cps); /* [HGM] EGT */
9826     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9827       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9828         SendToProgram(buf, cps);
9829     }
9830
9831     SendToProgram(cps->initString, cps);
9832     if (gameInfo.variant != VariantNormal &&
9833         gameInfo.variant != VariantLoadable
9834         /* [HGM] also send variant if board size non-standard */
9835         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9836                                             ) {
9837       char *v = VariantName(gameInfo.variant);
9838       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9839         /* [HGM] in protocol 1 we have to assume all variants valid */
9840         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9841         DisplayFatalError(buf, 0, 1);
9842         return;
9843       }
9844
9845       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9846       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9847       if( gameInfo.variant == VariantXiangqi )
9848            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9849       if( gameInfo.variant == VariantShogi )
9850            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9851       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9852            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9853       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9854           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9855            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9856       if( gameInfo.variant == VariantCourier )
9857            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9858       if( gameInfo.variant == VariantSuper )
9859            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9860       if( gameInfo.variant == VariantGreat )
9861            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9862       if( gameInfo.variant == VariantSChess )
9863            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9864       if( gameInfo.variant == VariantGrand )
9865            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9866
9867       if(overruled) {
9868         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9869                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9870            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9871            if(StrStr(cps->variants, b) == NULL) {
9872                // specific sized variant not known, check if general sizing allowed
9873                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9874                    if(StrStr(cps->variants, "boardsize") == NULL) {
9875                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9876                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9877                        DisplayFatalError(buf, 0, 1);
9878                        return;
9879                    }
9880                    /* [HGM] here we really should compare with the maximum supported board size */
9881                }
9882            }
9883       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9884       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9885       SendToProgram(buf, cps);
9886     }
9887     currentlyInitializedVariant = gameInfo.variant;
9888
9889     /* [HGM] send opening position in FRC to first engine */
9890     if(setup) {
9891           SendToProgram("force\n", cps);
9892           SendBoard(cps, 0);
9893           /* engine is now in force mode! Set flag to wake it up after first move. */
9894           setboardSpoiledMachineBlack = 1;
9895     }
9896
9897     if (cps->sendICS) {
9898       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9899       SendToProgram(buf, cps);
9900     }
9901     cps->maybeThinking = FALSE;
9902     cps->offeredDraw = 0;
9903     if (!appData.icsActive) {
9904         SendTimeControl(cps, movesPerSession, timeControl,
9905                         timeIncrement, appData.searchDepth,
9906                         searchTime);
9907     }
9908     if (appData.showThinking
9909         // [HGM] thinking: four options require thinking output to be sent
9910         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9911                                 ) {
9912         SendToProgram("post\n", cps);
9913     }
9914     SendToProgram("hard\n", cps);
9915     if (!appData.ponderNextMove) {
9916         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9917            it without being sure what state we are in first.  "hard"
9918            is not a toggle, so that one is OK.
9919          */
9920         SendToProgram("easy\n", cps);
9921     }
9922     if (cps->usePing) {
9923       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9924       SendToProgram(buf, cps);
9925     }
9926     cps->initDone = TRUE;
9927     ClearEngineOutputPane(cps == &second);
9928 }
9929
9930
9931 void
9932 StartChessProgram (ChessProgramState *cps)
9933 {
9934     char buf[MSG_SIZ];
9935     int err;
9936
9937     if (appData.noChessProgram) return;
9938     cps->initDone = FALSE;
9939
9940     if (strcmp(cps->host, "localhost") == 0) {
9941         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9942     } else if (*appData.remoteShell == NULLCHAR) {
9943         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9944     } else {
9945         if (*appData.remoteUser == NULLCHAR) {
9946           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9947                     cps->program);
9948         } else {
9949           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9950                     cps->host, appData.remoteUser, cps->program);
9951         }
9952         err = StartChildProcess(buf, "", &cps->pr);
9953     }
9954
9955     if (err != 0) {
9956       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9957         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9958         if(cps != &first) return;
9959         appData.noChessProgram = TRUE;
9960         ThawUI();
9961         SetNCPMode();
9962 //      DisplayFatalError(buf, err, 1);
9963 //      cps->pr = NoProc;
9964 //      cps->isr = NULL;
9965         return;
9966     }
9967
9968     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9969     if (cps->protocolVersion > 1) {
9970       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9971       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9972       cps->comboCnt = 0;  //                and values of combo boxes
9973       SendToProgram(buf, cps);
9974     } else {
9975       SendToProgram("xboard\n", cps);
9976     }
9977 }
9978
9979 void
9980 TwoMachinesEventIfReady P((void))
9981 {
9982   static int curMess = 0;
9983   if (first.lastPing != first.lastPong) {
9984     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9985     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9986     return;
9987   }
9988   if (second.lastPing != second.lastPong) {
9989     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9990     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9991     return;
9992   }
9993   DisplayMessage("", ""); curMess = 0;
9994   ThawUI();
9995   TwoMachinesEvent();
9996 }
9997
9998 char *
9999 MakeName (char *template)
10000 {
10001     time_t clock;
10002     struct tm *tm;
10003     static char buf[MSG_SIZ];
10004     char *p = buf;
10005     int i;
10006
10007     clock = time((time_t *)NULL);
10008     tm = localtime(&clock);
10009
10010     while(*p++ = *template++) if(p[-1] == '%') {
10011         switch(*template++) {
10012           case 0:   *p = 0; return buf;
10013           case 'Y': i = tm->tm_year+1900; break;
10014           case 'y': i = tm->tm_year-100; break;
10015           case 'M': i = tm->tm_mon+1; break;
10016           case 'd': i = tm->tm_mday; break;
10017           case 'h': i = tm->tm_hour; break;
10018           case 'm': i = tm->tm_min; break;
10019           case 's': i = tm->tm_sec; break;
10020           default:  i = 0;
10021         }
10022         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10023     }
10024     return buf;
10025 }
10026
10027 int
10028 CountPlayers (char *p)
10029 {
10030     int n = 0;
10031     while(p = strchr(p, '\n')) p++, n++; // count participants
10032     return n;
10033 }
10034
10035 FILE *
10036 WriteTourneyFile (char *results, FILE *f)
10037 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10038     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10039     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10040         // create a file with tournament description
10041         fprintf(f, "-participants {%s}\n", appData.participants);
10042         fprintf(f, "-seedBase %d\n", appData.seedBase);
10043         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10044         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10045         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10046         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10047         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10048         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10049         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10050         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10051         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10052         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10053         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10054         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10055         if(searchTime > 0)
10056                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10057         else {
10058                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10059                 fprintf(f, "-tc %s\n", appData.timeControl);
10060                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10061         }
10062         fprintf(f, "-results \"%s\"\n", results);
10063     }
10064     return f;
10065 }
10066
10067 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10068
10069 void
10070 Substitute (char *participants, int expunge)
10071 {
10072     int i, changed, changes=0, nPlayers=0;
10073     char *p, *q, *r, buf[MSG_SIZ];
10074     if(participants == NULL) return;
10075     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10076     r = p = participants; q = appData.participants;
10077     while(*p && *p == *q) {
10078         if(*p == '\n') r = p+1, nPlayers++;
10079         p++; q++;
10080     }
10081     if(*p) { // difference
10082         while(*p && *p++ != '\n');
10083         while(*q && *q++ != '\n');
10084       changed = nPlayers;
10085         changes = 1 + (strcmp(p, q) != 0);
10086     }
10087     if(changes == 1) { // a single engine mnemonic was changed
10088         q = r; while(*q) nPlayers += (*q++ == '\n');
10089         p = buf; while(*r && (*p = *r++) != '\n') p++;
10090         *p = NULLCHAR;
10091         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10092         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10093         if(mnemonic[i]) { // The substitute is valid
10094             FILE *f;
10095             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10096                 flock(fileno(f), LOCK_EX);
10097                 ParseArgsFromFile(f);
10098                 fseek(f, 0, SEEK_SET);
10099                 FREE(appData.participants); appData.participants = participants;
10100                 if(expunge) { // erase results of replaced engine
10101                     int len = strlen(appData.results), w, b, dummy;
10102                     for(i=0; i<len; i++) {
10103                         Pairing(i, nPlayers, &w, &b, &dummy);
10104                         if((w == changed || b == changed) && appData.results[i] == '*') {
10105                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10106                             fclose(f);
10107                             return;
10108                         }
10109                     }
10110                     for(i=0; i<len; i++) {
10111                         Pairing(i, nPlayers, &w, &b, &dummy);
10112                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10113                     }
10114                 }
10115                 WriteTourneyFile(appData.results, f);
10116                 fclose(f); // release lock
10117                 return;
10118             }
10119         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10120     }
10121     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10122     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10123     free(participants);
10124     return;
10125 }
10126
10127 int
10128 CheckPlayers (char *participants)
10129 {
10130         int i;
10131         char buf[MSG_SIZ], *p;
10132         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10133         while(p = strchr(participants, '\n')) {
10134             *p = NULLCHAR;
10135             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10136             if(!mnemonic[i]) {
10137                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10138                 *p = '\n';
10139                 DisplayError(buf, 0);
10140                 return 1;
10141             }
10142             *p = '\n';
10143             participants = p + 1;
10144         }
10145         return 0;
10146 }
10147
10148 int
10149 CreateTourney (char *name)
10150 {
10151         FILE *f;
10152         if(matchMode && strcmp(name, appData.tourneyFile)) {
10153              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10154         }
10155         if(name[0] == NULLCHAR) {
10156             if(appData.participants[0])
10157                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10158             return 0;
10159         }
10160         f = fopen(name, "r");
10161         if(f) { // file exists
10162             ASSIGN(appData.tourneyFile, name);
10163             ParseArgsFromFile(f); // parse it
10164         } else {
10165             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10166             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10167                 DisplayError(_("Not enough participants"), 0);
10168                 return 0;
10169             }
10170             if(CheckPlayers(appData.participants)) return 0;
10171             ASSIGN(appData.tourneyFile, name);
10172             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10173             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10174         }
10175         fclose(f);
10176         appData.noChessProgram = FALSE;
10177         appData.clockMode = TRUE;
10178         SetGNUMode();
10179         return 1;
10180 }
10181
10182 int
10183 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10184 {
10185     char buf[MSG_SIZ], *p, *q;
10186     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10187     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10188     skip = !all && group[0]; // if group requested, we start in skip mode
10189     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10190         p = names; q = buf; header = 0;
10191         while(*p && *p != '\n') *q++ = *p++;
10192         *q = 0;
10193         if(*p == '\n') p++;
10194         if(buf[0] == '#') {
10195             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10196             depth++; // we must be entering a new group
10197             if(all) continue; // suppress printing group headers when complete list requested
10198             header = 1;
10199             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10200         }
10201         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10202         if(engineList[i]) free(engineList[i]);
10203         engineList[i] = strdup(buf);
10204         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10205         if(engineMnemonic[i]) free(engineMnemonic[i]);
10206         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10207             strcat(buf, " (");
10208             sscanf(q + 8, "%s", buf + strlen(buf));
10209             strcat(buf, ")");
10210         }
10211         engineMnemonic[i] = strdup(buf);
10212         i++;
10213     }
10214     engineList[i] = engineMnemonic[i] = NULL;
10215     return i;
10216 }
10217
10218 // following implemented as macro to avoid type limitations
10219 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10220
10221 void
10222 SwapEngines (int n)
10223 {   // swap settings for first engine and other engine (so far only some selected options)
10224     int h;
10225     char *p;
10226     if(n == 0) return;
10227     SWAP(directory, p)
10228     SWAP(chessProgram, p)
10229     SWAP(isUCI, h)
10230     SWAP(hasOwnBookUCI, h)
10231     SWAP(protocolVersion, h)
10232     SWAP(reuse, h)
10233     SWAP(scoreIsAbsolute, h)
10234     SWAP(timeOdds, h)
10235     SWAP(logo, p)
10236     SWAP(pgnName, p)
10237     SWAP(pvSAN, h)
10238     SWAP(engOptions, p)
10239     SWAP(engInitString, p)
10240     SWAP(computerString, p)
10241     SWAP(features, p)
10242     SWAP(fenOverride, p)
10243     SWAP(NPS, h)
10244     SWAP(accumulateTC, h)
10245     SWAP(host, p)
10246 }
10247
10248 int
10249 GetEngineLine (char *s, int n)
10250 {
10251     int i;
10252     char buf[MSG_SIZ];
10253     extern char *icsNames;
10254     if(!s || !*s) return 0;
10255     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10256     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10257     if(!mnemonic[i]) return 0;
10258     if(n == 11) return 1; // just testing if there was a match
10259     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10260     if(n == 1) SwapEngines(n);
10261     ParseArgsFromString(buf);
10262     if(n == 1) SwapEngines(n);
10263     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10264         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10265         ParseArgsFromString(buf);
10266     }
10267     return 1;
10268 }
10269
10270 int
10271 SetPlayer (int player, char *p)
10272 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10273     int i;
10274     char buf[MSG_SIZ], *engineName;
10275     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10276     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10277     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10278     if(mnemonic[i]) {
10279         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10280         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10281         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10282         ParseArgsFromString(buf);
10283     }
10284     free(engineName);
10285     return i;
10286 }
10287
10288 char *recentEngines;
10289
10290 void
10291 RecentEngineEvent (int nr)
10292 {
10293     int n;
10294 //    SwapEngines(1); // bump first to second
10295 //    ReplaceEngine(&second, 1); // and load it there
10296     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10297     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10298     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10299         ReplaceEngine(&first, 0);
10300         FloatToFront(&appData.recentEngineList, command[n]);
10301     }
10302 }
10303
10304 int
10305 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10306 {   // determine players from game number
10307     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10308
10309     if(appData.tourneyType == 0) {
10310         roundsPerCycle = (nPlayers - 1) | 1;
10311         pairingsPerRound = nPlayers / 2;
10312     } else if(appData.tourneyType > 0) {
10313         roundsPerCycle = nPlayers - appData.tourneyType;
10314         pairingsPerRound = appData.tourneyType;
10315     }
10316     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10317     gamesPerCycle = gamesPerRound * roundsPerCycle;
10318     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10319     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10320     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10321     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10322     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10323     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10324
10325     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10326     if(appData.roundSync) *syncInterval = gamesPerRound;
10327
10328     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10329
10330     if(appData.tourneyType == 0) {
10331         if(curPairing == (nPlayers-1)/2 ) {
10332             *whitePlayer = curRound;
10333             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10334         } else {
10335             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10336             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10337             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10338             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10339         }
10340     } else if(appData.tourneyType > 1) {
10341         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10342         *whitePlayer = curRound + appData.tourneyType;
10343     } else if(appData.tourneyType > 0) {
10344         *whitePlayer = curPairing;
10345         *blackPlayer = curRound + appData.tourneyType;
10346     }
10347
10348     // take care of white/black alternation per round. 
10349     // For cycles and games this is already taken care of by default, derived from matchGame!
10350     return curRound & 1;
10351 }
10352
10353 int
10354 NextTourneyGame (int nr, int *swapColors)
10355 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10356     char *p, *q;
10357     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10358     FILE *tf;
10359     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10360     tf = fopen(appData.tourneyFile, "r");
10361     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10362     ParseArgsFromFile(tf); fclose(tf);
10363     InitTimeControls(); // TC might be altered from tourney file
10364
10365     nPlayers = CountPlayers(appData.participants); // count participants
10366     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10367     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10368
10369     if(syncInterval) {
10370         p = q = appData.results;
10371         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10372         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10373             DisplayMessage(_("Waiting for other game(s)"),"");
10374             waitingForGame = TRUE;
10375             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10376             return 0;
10377         }
10378         waitingForGame = FALSE;
10379     }
10380
10381     if(appData.tourneyType < 0) {
10382         if(nr>=0 && !pairingReceived) {
10383             char buf[1<<16];
10384             if(pairing.pr == NoProc) {
10385                 if(!appData.pairingEngine[0]) {
10386                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10387                     return 0;
10388                 }
10389                 StartChessProgram(&pairing); // starts the pairing engine
10390             }
10391             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10392             SendToProgram(buf, &pairing);
10393             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10394             SendToProgram(buf, &pairing);
10395             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10396         }
10397         pairingReceived = 0;                              // ... so we continue here 
10398         *swapColors = 0;
10399         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10400         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10401         matchGame = 1; roundNr = nr / syncInterval + 1;
10402     }
10403
10404     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10405
10406     // redefine engines, engine dir, etc.
10407     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10408     if(first.pr == NoProc) {
10409       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10410       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10411     }
10412     if(second.pr == NoProc) {
10413       SwapEngines(1);
10414       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10415       SwapEngines(1);         // and make that valid for second engine by swapping
10416       InitEngine(&second, 1);
10417     }
10418     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10419     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10420     return 1;
10421 }
10422
10423 void
10424 NextMatchGame ()
10425 {   // performs game initialization that does not invoke engines, and then tries to start the game
10426     int res, firstWhite, swapColors = 0;
10427     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10428     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
10429         char buf[MSG_SIZ];
10430         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10431         if(strcmp(buf, currentDebugFile)) { // name has changed
10432             FILE *f = fopen(buf, "w");
10433             if(f) { // if opening the new file failed, just keep using the old one
10434                 ASSIGN(currentDebugFile, buf);
10435                 fclose(debugFP);
10436                 debugFP = f;
10437             }
10438             if(appData.serverFileName) {
10439                 if(serverFP) fclose(serverFP);
10440                 serverFP = fopen(appData.serverFileName, "w");
10441                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10442                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10443             }
10444         }
10445     }
10446     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10447     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10448     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10449     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10450     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10451     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10452     Reset(FALSE, first.pr != NoProc);
10453     res = LoadGameOrPosition(matchGame); // setup game
10454     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10455     if(!res) return; // abort when bad game/pos file
10456     TwoMachinesEvent();
10457 }
10458
10459 void
10460 UserAdjudicationEvent (int result)
10461 {
10462     ChessMove gameResult = GameIsDrawn;
10463
10464     if( result > 0 ) {
10465         gameResult = WhiteWins;
10466     }
10467     else if( result < 0 ) {
10468         gameResult = BlackWins;
10469     }
10470
10471     if( gameMode == TwoMachinesPlay ) {
10472         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10473     }
10474 }
10475
10476
10477 // [HGM] save: calculate checksum of game to make games easily identifiable
10478 int
10479 StringCheckSum (char *s)
10480 {
10481         int i = 0;
10482         if(s==NULL) return 0;
10483         while(*s) i = i*259 + *s++;
10484         return i;
10485 }
10486
10487 int
10488 GameCheckSum ()
10489 {
10490         int i, sum=0;
10491         for(i=backwardMostMove; i<forwardMostMove; i++) {
10492                 sum += pvInfoList[i].depth;
10493                 sum += StringCheckSum(parseList[i]);
10494                 sum += StringCheckSum(commentList[i]);
10495                 sum *= 261;
10496         }
10497         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10498         return sum + StringCheckSum(commentList[i]);
10499 } // end of save patch
10500
10501 void
10502 GameEnds (ChessMove result, char *resultDetails, int whosays)
10503 {
10504     GameMode nextGameMode;
10505     int isIcsGame;
10506     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10507
10508     if(endingGame) return; /* [HGM] crash: forbid recursion */
10509     endingGame = 1;
10510     if(twoBoards) { // [HGM] dual: switch back to one board
10511         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10512         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10513     }
10514     if (appData.debugMode) {
10515       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10516               result, resultDetails ? resultDetails : "(null)", whosays);
10517     }
10518
10519     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10520
10521     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10522
10523     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10524         /* If we are playing on ICS, the server decides when the
10525            game is over, but the engine can offer to draw, claim
10526            a draw, or resign.
10527          */
10528 #if ZIPPY
10529         if (appData.zippyPlay && first.initDone) {
10530             if (result == GameIsDrawn) {
10531                 /* In case draw still needs to be claimed */
10532                 SendToICS(ics_prefix);
10533                 SendToICS("draw\n");
10534             } else if (StrCaseStr(resultDetails, "resign")) {
10535                 SendToICS(ics_prefix);
10536                 SendToICS("resign\n");
10537             }
10538         }
10539 #endif
10540         endingGame = 0; /* [HGM] crash */
10541         return;
10542     }
10543
10544     /* If we're loading the game from a file, stop */
10545     if (whosays == GE_FILE) {
10546       (void) StopLoadGameTimer();
10547       gameFileFP = NULL;
10548     }
10549
10550     /* Cancel draw offers */
10551     first.offeredDraw = second.offeredDraw = 0;
10552
10553     /* If this is an ICS game, only ICS can really say it's done;
10554        if not, anyone can. */
10555     isIcsGame = (gameMode == IcsPlayingWhite ||
10556                  gameMode == IcsPlayingBlack ||
10557                  gameMode == IcsObserving    ||
10558                  gameMode == IcsExamining);
10559
10560     if (!isIcsGame || whosays == GE_ICS) {
10561         /* OK -- not an ICS game, or ICS said it was done */
10562         StopClocks();
10563         if (!isIcsGame && !appData.noChessProgram)
10564           SetUserThinkingEnables();
10565
10566         /* [HGM] if a machine claims the game end we verify this claim */
10567         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10568             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10569                 char claimer;
10570                 ChessMove trueResult = (ChessMove) -1;
10571
10572                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10573                                             first.twoMachinesColor[0] :
10574                                             second.twoMachinesColor[0] ;
10575
10576                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10577                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10578                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10579                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10580                 } else
10581                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10582                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10583                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10584                 } else
10585                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10586                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10587                 }
10588
10589                 // now verify win claims, but not in drop games, as we don't understand those yet
10590                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10591                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10592                     (result == WhiteWins && claimer == 'w' ||
10593                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10594                       if (appData.debugMode) {
10595                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10596                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10597                       }
10598                       if(result != trueResult) {
10599                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10600                               result = claimer == 'w' ? BlackWins : WhiteWins;
10601                               resultDetails = buf;
10602                       }
10603                 } else
10604                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10605                     && (forwardMostMove <= backwardMostMove ||
10606                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10607                         (claimer=='b')==(forwardMostMove&1))
10608                                                                                   ) {
10609                       /* [HGM] verify: draws that were not flagged are false claims */
10610                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10611                       result = claimer == 'w' ? BlackWins : WhiteWins;
10612                       resultDetails = buf;
10613                 }
10614                 /* (Claiming a loss is accepted no questions asked!) */
10615             }
10616             /* [HGM] bare: don't allow bare King to win */
10617             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10618                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10619                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10620                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10621                && result != GameIsDrawn)
10622             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10623                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10624                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10625                         if(p >= 0 && p <= (int)WhiteKing) k++;
10626                 }
10627                 if (appData.debugMode) {
10628                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10629                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10630                 }
10631                 if(k <= 1) {
10632                         result = GameIsDrawn;
10633                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10634                         resultDetails = buf;
10635                 }
10636             }
10637         }
10638
10639
10640         if(serverMoves != NULL && !loadFlag) { char c = '=';
10641             if(result==WhiteWins) c = '+';
10642             if(result==BlackWins) c = '-';
10643             if(resultDetails != NULL)
10644                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10645         }
10646         if (resultDetails != NULL) {
10647             gameInfo.result = result;
10648             gameInfo.resultDetails = StrSave(resultDetails);
10649
10650             /* display last move only if game was not loaded from file */
10651             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10652                 DisplayMove(currentMove - 1);
10653
10654             if (forwardMostMove != 0) {
10655                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10656                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10657                                                                 ) {
10658                     if (*appData.saveGameFile != NULLCHAR) {
10659                         SaveGameToFile(appData.saveGameFile, TRUE);
10660                     } else if (appData.autoSaveGames) {
10661                         AutoSaveGame();
10662                     }
10663                     if (*appData.savePositionFile != NULLCHAR) {
10664                         SavePositionToFile(appData.savePositionFile);
10665                     }
10666                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10667                 }
10668             }
10669
10670             /* Tell program how game ended in case it is learning */
10671             /* [HGM] Moved this to after saving the PGN, just in case */
10672             /* engine died and we got here through time loss. In that */
10673             /* case we will get a fatal error writing the pipe, which */
10674             /* would otherwise lose us the PGN.                       */
10675             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10676             /* output during GameEnds should never be fatal anymore   */
10677             if (gameMode == MachinePlaysWhite ||
10678                 gameMode == MachinePlaysBlack ||
10679                 gameMode == TwoMachinesPlay ||
10680                 gameMode == IcsPlayingWhite ||
10681                 gameMode == IcsPlayingBlack ||
10682                 gameMode == BeginningOfGame) {
10683                 char buf[MSG_SIZ];
10684                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10685                         resultDetails);
10686                 if (first.pr != NoProc) {
10687                     SendToProgram(buf, &first);
10688                 }
10689                 if (second.pr != NoProc &&
10690                     gameMode == TwoMachinesPlay) {
10691                     SendToProgram(buf, &second);
10692                 }
10693             }
10694         }
10695
10696         if (appData.icsActive) {
10697             if (appData.quietPlay &&
10698                 (gameMode == IcsPlayingWhite ||
10699                  gameMode == IcsPlayingBlack)) {
10700                 SendToICS(ics_prefix);
10701                 SendToICS("set shout 1\n");
10702             }
10703             nextGameMode = IcsIdle;
10704             ics_user_moved = FALSE;
10705             /* clean up premove.  It's ugly when the game has ended and the
10706              * premove highlights are still on the board.
10707              */
10708             if (gotPremove) {
10709               gotPremove = FALSE;
10710               ClearPremoveHighlights();
10711               DrawPosition(FALSE, boards[currentMove]);
10712             }
10713             if (whosays == GE_ICS) {
10714                 switch (result) {
10715                 case WhiteWins:
10716                     if (gameMode == IcsPlayingWhite)
10717                         PlayIcsWinSound();
10718                     else if(gameMode == IcsPlayingBlack)
10719                         PlayIcsLossSound();
10720                     break;
10721                 case BlackWins:
10722                     if (gameMode == IcsPlayingBlack)
10723                         PlayIcsWinSound();
10724                     else if(gameMode == IcsPlayingWhite)
10725                         PlayIcsLossSound();
10726                     break;
10727                 case GameIsDrawn:
10728                     PlayIcsDrawSound();
10729                     break;
10730                 default:
10731                     PlayIcsUnfinishedSound();
10732                 }
10733             }
10734         } else if (gameMode == EditGame ||
10735                    gameMode == PlayFromGameFile ||
10736                    gameMode == AnalyzeMode ||
10737                    gameMode == AnalyzeFile) {
10738             nextGameMode = gameMode;
10739         } else {
10740             nextGameMode = EndOfGame;
10741         }
10742         pausing = FALSE;
10743         ModeHighlight();
10744     } else {
10745         nextGameMode = gameMode;
10746     }
10747
10748     if (appData.noChessProgram) {
10749         gameMode = nextGameMode;
10750         ModeHighlight();
10751         endingGame = 0; /* [HGM] crash */
10752         return;
10753     }
10754
10755     if (first.reuse) {
10756         /* Put first chess program into idle state */
10757         if (first.pr != NoProc &&
10758             (gameMode == MachinePlaysWhite ||
10759              gameMode == MachinePlaysBlack ||
10760              gameMode == TwoMachinesPlay ||
10761              gameMode == IcsPlayingWhite ||
10762              gameMode == IcsPlayingBlack ||
10763              gameMode == BeginningOfGame)) {
10764             SendToProgram("force\n", &first);
10765             if (first.usePing) {
10766               char buf[MSG_SIZ];
10767               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10768               SendToProgram(buf, &first);
10769             }
10770         }
10771     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10772         /* Kill off first chess program */
10773         if (first.isr != NULL)
10774           RemoveInputSource(first.isr);
10775         first.isr = NULL;
10776
10777         if (first.pr != NoProc) {
10778             ExitAnalyzeMode();
10779             DoSleep( appData.delayBeforeQuit );
10780             SendToProgram("quit\n", &first);
10781             DoSleep( appData.delayAfterQuit );
10782             DestroyChildProcess(first.pr, first.useSigterm);
10783         }
10784         first.pr = NoProc;
10785     }
10786     if (second.reuse) {
10787         /* Put second chess program into idle state */
10788         if (second.pr != NoProc &&
10789             gameMode == TwoMachinesPlay) {
10790             SendToProgram("force\n", &second);
10791             if (second.usePing) {
10792               char buf[MSG_SIZ];
10793               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10794               SendToProgram(buf, &second);
10795             }
10796         }
10797     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10798         /* Kill off second chess program */
10799         if (second.isr != NULL)
10800           RemoveInputSource(second.isr);
10801         second.isr = NULL;
10802
10803         if (second.pr != NoProc) {
10804             DoSleep( appData.delayBeforeQuit );
10805             SendToProgram("quit\n", &second);
10806             DoSleep( appData.delayAfterQuit );
10807             DestroyChildProcess(second.pr, second.useSigterm);
10808         }
10809         second.pr = NoProc;
10810     }
10811
10812     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10813         char resChar = '=';
10814         switch (result) {
10815         case WhiteWins:
10816           resChar = '+';
10817           if (first.twoMachinesColor[0] == 'w') {
10818             first.matchWins++;
10819           } else {
10820             second.matchWins++;
10821           }
10822           break;
10823         case BlackWins:
10824           resChar = '-';
10825           if (first.twoMachinesColor[0] == 'b') {
10826             first.matchWins++;
10827           } else {
10828             second.matchWins++;
10829           }
10830           break;
10831         case GameUnfinished:
10832           resChar = ' ';
10833         default:
10834           break;
10835         }
10836
10837         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10838         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10839             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10840             ReserveGame(nextGame, resChar); // sets nextGame
10841             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10842             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10843         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10844
10845         if (nextGame <= appData.matchGames && !abortMatch) {
10846             gameMode = nextGameMode;
10847             matchGame = nextGame; // this will be overruled in tourney mode!
10848             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10849             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10850             endingGame = 0; /* [HGM] crash */
10851             return;
10852         } else {
10853             gameMode = nextGameMode;
10854             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10855                      first.tidy, second.tidy,
10856                      first.matchWins, second.matchWins,
10857                      appData.matchGames - (first.matchWins + second.matchWins));
10858             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10859             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10860             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10861             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10862                 first.twoMachinesColor = "black\n";
10863                 second.twoMachinesColor = "white\n";
10864             } else {
10865                 first.twoMachinesColor = "white\n";
10866                 second.twoMachinesColor = "black\n";
10867             }
10868         }
10869     }
10870     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10871         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10872       ExitAnalyzeMode();
10873     gameMode = nextGameMode;
10874     ModeHighlight();
10875     endingGame = 0;  /* [HGM] crash */
10876     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10877         if(matchMode == TRUE) { // match through command line: exit with or without popup
10878             if(ranking) {
10879                 ToNrEvent(forwardMostMove);
10880                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10881                 else ExitEvent(0);
10882             } else DisplayFatalError(buf, 0, 0);
10883         } else { // match through menu; just stop, with or without popup
10884             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10885             ModeHighlight();
10886             if(ranking){
10887                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10888             } else DisplayNote(buf);
10889       }
10890       if(ranking) free(ranking);
10891     }
10892 }
10893
10894 /* Assumes program was just initialized (initString sent).
10895    Leaves program in force mode. */
10896 void
10897 FeedMovesToProgram (ChessProgramState *cps, int upto)
10898 {
10899     int i;
10900
10901     if (appData.debugMode)
10902       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10903               startedFromSetupPosition ? "position and " : "",
10904               backwardMostMove, upto, cps->which);
10905     if(currentlyInitializedVariant != gameInfo.variant) {
10906       char buf[MSG_SIZ];
10907         // [HGM] variantswitch: make engine aware of new variant
10908         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10909                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10910         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10911         SendToProgram(buf, cps);
10912         currentlyInitializedVariant = gameInfo.variant;
10913     }
10914     SendToProgram("force\n", cps);
10915     if (startedFromSetupPosition) {
10916         SendBoard(cps, backwardMostMove);
10917     if (appData.debugMode) {
10918         fprintf(debugFP, "feedMoves\n");
10919     }
10920     }
10921     for (i = backwardMostMove; i < upto; i++) {
10922         SendMoveToProgram(i, cps);
10923     }
10924 }
10925
10926
10927 int
10928 ResurrectChessProgram ()
10929 {
10930      /* The chess program may have exited.
10931         If so, restart it and feed it all the moves made so far. */
10932     static int doInit = 0;
10933
10934     if (appData.noChessProgram) return 1;
10935
10936     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10937         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10938         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10939         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10940     } else {
10941         if (first.pr != NoProc) return 1;
10942         StartChessProgram(&first);
10943     }
10944     InitChessProgram(&first, FALSE);
10945     FeedMovesToProgram(&first, currentMove);
10946
10947     if (!first.sendTime) {
10948         /* can't tell gnuchess what its clock should read,
10949            so we bow to its notion. */
10950         ResetClocks();
10951         timeRemaining[0][currentMove] = whiteTimeRemaining;
10952         timeRemaining[1][currentMove] = blackTimeRemaining;
10953     }
10954
10955     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10956                 appData.icsEngineAnalyze) && first.analysisSupport) {
10957       SendToProgram("analyze\n", &first);
10958       first.analyzing = TRUE;
10959     }
10960     return 1;
10961 }
10962
10963 /*
10964  * Button procedures
10965  */
10966 void
10967 Reset (int redraw, int init)
10968 {
10969     int i;
10970
10971     if (appData.debugMode) {
10972         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10973                 redraw, init, gameMode);
10974     }
10975     CleanupTail(); // [HGM] vari: delete any stored variations
10976     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10977     pausing = pauseExamInvalid = FALSE;
10978     startedFromSetupPosition = blackPlaysFirst = FALSE;
10979     firstMove = TRUE;
10980     whiteFlag = blackFlag = FALSE;
10981     userOfferedDraw = FALSE;
10982     hintRequested = bookRequested = FALSE;
10983     first.maybeThinking = FALSE;
10984     second.maybeThinking = FALSE;
10985     first.bookSuspend = FALSE; // [HGM] book
10986     second.bookSuspend = FALSE;
10987     thinkOutput[0] = NULLCHAR;
10988     lastHint[0] = NULLCHAR;
10989     ClearGameInfo(&gameInfo);
10990     gameInfo.variant = StringToVariant(appData.variant);
10991     ics_user_moved = ics_clock_paused = FALSE;
10992     ics_getting_history = H_FALSE;
10993     ics_gamenum = -1;
10994     white_holding[0] = black_holding[0] = NULLCHAR;
10995     ClearProgramStats();
10996     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10997
10998     ResetFrontEnd();
10999     ClearHighlights();
11000     flipView = appData.flipView;
11001     ClearPremoveHighlights();
11002     gotPremove = FALSE;
11003     alarmSounded = FALSE;
11004
11005     GameEnds(EndOfFile, NULL, GE_PLAYER);
11006     if(appData.serverMovesName != NULL) {
11007         /* [HGM] prepare to make moves file for broadcasting */
11008         clock_t t = clock();
11009         if(serverMoves != NULL) fclose(serverMoves);
11010         serverMoves = fopen(appData.serverMovesName, "r");
11011         if(serverMoves != NULL) {
11012             fclose(serverMoves);
11013             /* delay 15 sec before overwriting, so all clients can see end */
11014             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11015         }
11016         serverMoves = fopen(appData.serverMovesName, "w");
11017     }
11018
11019     ExitAnalyzeMode();
11020     gameMode = BeginningOfGame;
11021     ModeHighlight();
11022     if(appData.icsActive) gameInfo.variant = VariantNormal;
11023     currentMove = forwardMostMove = backwardMostMove = 0;
11024     MarkTargetSquares(1);
11025     InitPosition(redraw);
11026     for (i = 0; i < MAX_MOVES; i++) {
11027         if (commentList[i] != NULL) {
11028             free(commentList[i]);
11029             commentList[i] = NULL;
11030         }
11031     }
11032     ResetClocks();
11033     timeRemaining[0][0] = whiteTimeRemaining;
11034     timeRemaining[1][0] = blackTimeRemaining;
11035
11036     if (first.pr == NoProc) {
11037         StartChessProgram(&first);
11038     }
11039     if (init) {
11040             InitChessProgram(&first, startedFromSetupPosition);
11041     }
11042     DisplayTitle("");
11043     DisplayMessage("", "");
11044     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11045     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11046     ClearMap();        // [HGM] exclude: invalidate map
11047 }
11048
11049 void
11050 AutoPlayGameLoop ()
11051 {
11052     for (;;) {
11053         if (!AutoPlayOneMove())
11054           return;
11055         if (matchMode || appData.timeDelay == 0)
11056           continue;
11057         if (appData.timeDelay < 0)
11058           return;
11059         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11060         break;
11061     }
11062 }
11063
11064 void
11065 AnalyzeNextGame()
11066 {
11067     ReloadGame(1); // next game
11068 }
11069
11070 int
11071 AutoPlayOneMove ()
11072 {
11073     int fromX, fromY, toX, toY;
11074
11075     if (appData.debugMode) {
11076       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11077     }
11078
11079     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11080       return FALSE;
11081
11082     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11083       pvInfoList[currentMove].depth = programStats.depth;
11084       pvInfoList[currentMove].score = programStats.score;
11085       pvInfoList[currentMove].time  = 0;
11086       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11087     }
11088
11089     if (currentMove >= forwardMostMove) {
11090       if(gameMode == AnalyzeFile) {
11091           if(appData.loadGameIndex == -1) {
11092             GameEnds(EndOfFile, NULL, GE_FILE);
11093           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11094           } else {
11095           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11096         }
11097       }
11098 //      gameMode = EndOfGame;
11099 //      ModeHighlight();
11100
11101       /* [AS] Clear current move marker at the end of a game */
11102       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11103
11104       return FALSE;
11105     }
11106
11107     toX = moveList[currentMove][2] - AAA;
11108     toY = moveList[currentMove][3] - ONE;
11109
11110     if (moveList[currentMove][1] == '@') {
11111         if (appData.highlightLastMove) {
11112             SetHighlights(-1, -1, toX, toY);
11113         }
11114     } else {
11115         fromX = moveList[currentMove][0] - AAA;
11116         fromY = moveList[currentMove][1] - ONE;
11117
11118         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11119
11120         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11121
11122         if (appData.highlightLastMove) {
11123             SetHighlights(fromX, fromY, toX, toY);
11124         }
11125     }
11126     DisplayMove(currentMove);
11127     SendMoveToProgram(currentMove++, &first);
11128     DisplayBothClocks();
11129     DrawPosition(FALSE, boards[currentMove]);
11130     // [HGM] PV info: always display, routine tests if empty
11131     DisplayComment(currentMove - 1, commentList[currentMove]);
11132     return TRUE;
11133 }
11134
11135
11136 int
11137 LoadGameOneMove (ChessMove readAhead)
11138 {
11139     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11140     char promoChar = NULLCHAR;
11141     ChessMove moveType;
11142     char move[MSG_SIZ];
11143     char *p, *q;
11144
11145     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11146         gameMode != AnalyzeMode && gameMode != Training) {
11147         gameFileFP = NULL;
11148         return FALSE;
11149     }
11150
11151     yyboardindex = forwardMostMove;
11152     if (readAhead != EndOfFile) {
11153       moveType = readAhead;
11154     } else {
11155       if (gameFileFP == NULL)
11156           return FALSE;
11157       moveType = (ChessMove) Myylex();
11158     }
11159
11160     done = FALSE;
11161     switch (moveType) {
11162       case Comment:
11163         if (appData.debugMode)
11164           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11165         p = yy_text;
11166
11167         /* append the comment but don't display it */
11168         AppendComment(currentMove, p, FALSE);
11169         return TRUE;
11170
11171       case WhiteCapturesEnPassant:
11172       case BlackCapturesEnPassant:
11173       case WhitePromotion:
11174       case BlackPromotion:
11175       case WhiteNonPromotion:
11176       case BlackNonPromotion:
11177       case NormalMove:
11178       case WhiteKingSideCastle:
11179       case WhiteQueenSideCastle:
11180       case BlackKingSideCastle:
11181       case BlackQueenSideCastle:
11182       case WhiteKingSideCastleWild:
11183       case WhiteQueenSideCastleWild:
11184       case BlackKingSideCastleWild:
11185       case BlackQueenSideCastleWild:
11186       /* PUSH Fabien */
11187       case WhiteHSideCastleFR:
11188       case WhiteASideCastleFR:
11189       case BlackHSideCastleFR:
11190       case BlackASideCastleFR:
11191       /* POP Fabien */
11192         if (appData.debugMode)
11193           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11194         fromX = currentMoveString[0] - AAA;
11195         fromY = currentMoveString[1] - ONE;
11196         toX = currentMoveString[2] - AAA;
11197         toY = currentMoveString[3] - ONE;
11198         promoChar = currentMoveString[4];
11199         break;
11200
11201       case WhiteDrop:
11202       case BlackDrop:
11203         if (appData.debugMode)
11204           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11205         fromX = moveType == WhiteDrop ?
11206           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11207         (int) CharToPiece(ToLower(currentMoveString[0]));
11208         fromY = DROP_RANK;
11209         toX = currentMoveString[2] - AAA;
11210         toY = currentMoveString[3] - ONE;
11211         break;
11212
11213       case WhiteWins:
11214       case BlackWins:
11215       case GameIsDrawn:
11216       case GameUnfinished:
11217         if (appData.debugMode)
11218           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11219         p = strchr(yy_text, '{');
11220         if (p == NULL) p = strchr(yy_text, '(');
11221         if (p == NULL) {
11222             p = yy_text;
11223             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11224         } else {
11225             q = strchr(p, *p == '{' ? '}' : ')');
11226             if (q != NULL) *q = NULLCHAR;
11227             p++;
11228         }
11229         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11230         GameEnds(moveType, p, GE_FILE);
11231         done = TRUE;
11232         if (cmailMsgLoaded) {
11233             ClearHighlights();
11234             flipView = WhiteOnMove(currentMove);
11235             if (moveType == GameUnfinished) flipView = !flipView;
11236             if (appData.debugMode)
11237               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11238         }
11239         break;
11240
11241       case EndOfFile:
11242         if (appData.debugMode)
11243           fprintf(debugFP, "Parser hit end of file\n");
11244         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11245           case MT_NONE:
11246           case MT_CHECK:
11247             break;
11248           case MT_CHECKMATE:
11249           case MT_STAINMATE:
11250             if (WhiteOnMove(currentMove)) {
11251                 GameEnds(BlackWins, "Black mates", GE_FILE);
11252             } else {
11253                 GameEnds(WhiteWins, "White mates", GE_FILE);
11254             }
11255             break;
11256           case MT_STALEMATE:
11257             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11258             break;
11259         }
11260         done = TRUE;
11261         break;
11262
11263       case MoveNumberOne:
11264         if (lastLoadGameStart == GNUChessGame) {
11265             /* GNUChessGames have numbers, but they aren't move numbers */
11266             if (appData.debugMode)
11267               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11268                       yy_text, (int) moveType);
11269             return LoadGameOneMove(EndOfFile); /* tail recursion */
11270         }
11271         /* else fall thru */
11272
11273       case XBoardGame:
11274       case GNUChessGame:
11275       case PGNTag:
11276         /* Reached start of next game in file */
11277         if (appData.debugMode)
11278           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11279         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11280           case MT_NONE:
11281           case MT_CHECK:
11282             break;
11283           case MT_CHECKMATE:
11284           case MT_STAINMATE:
11285             if (WhiteOnMove(currentMove)) {
11286                 GameEnds(BlackWins, "Black mates", GE_FILE);
11287             } else {
11288                 GameEnds(WhiteWins, "White mates", GE_FILE);
11289             }
11290             break;
11291           case MT_STALEMATE:
11292             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11293             break;
11294         }
11295         done = TRUE;
11296         break;
11297
11298       case PositionDiagram:     /* should not happen; ignore */
11299       case ElapsedTime:         /* ignore */
11300       case NAG:                 /* ignore */
11301         if (appData.debugMode)
11302           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11303                   yy_text, (int) moveType);
11304         return LoadGameOneMove(EndOfFile); /* tail recursion */
11305
11306       case IllegalMove:
11307         if (appData.testLegality) {
11308             if (appData.debugMode)
11309               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11310             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11311                     (forwardMostMove / 2) + 1,
11312                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11313             DisplayError(move, 0);
11314             done = TRUE;
11315         } else {
11316             if (appData.debugMode)
11317               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11318                       yy_text, currentMoveString);
11319             fromX = currentMoveString[0] - AAA;
11320             fromY = currentMoveString[1] - ONE;
11321             toX = currentMoveString[2] - AAA;
11322             toY = currentMoveString[3] - ONE;
11323             promoChar = currentMoveString[4];
11324         }
11325         break;
11326
11327       case AmbiguousMove:
11328         if (appData.debugMode)
11329           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11330         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11331                 (forwardMostMove / 2) + 1,
11332                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11333         DisplayError(move, 0);
11334         done = TRUE;
11335         break;
11336
11337       default:
11338       case ImpossibleMove:
11339         if (appData.debugMode)
11340           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11341         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11342                 (forwardMostMove / 2) + 1,
11343                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11344         DisplayError(move, 0);
11345         done = TRUE;
11346         break;
11347     }
11348
11349     if (done) {
11350         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11351             DrawPosition(FALSE, boards[currentMove]);
11352             DisplayBothClocks();
11353             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11354               DisplayComment(currentMove - 1, commentList[currentMove]);
11355         }
11356         (void) StopLoadGameTimer();
11357         gameFileFP = NULL;
11358         cmailOldMove = forwardMostMove;
11359         return FALSE;
11360     } else {
11361         /* currentMoveString is set as a side-effect of yylex */
11362
11363         thinkOutput[0] = NULLCHAR;
11364         MakeMove(fromX, fromY, toX, toY, promoChar);
11365         currentMove = forwardMostMove;
11366         return TRUE;
11367     }
11368 }
11369
11370 /* Load the nth game from the given file */
11371 int
11372 LoadGameFromFile (char *filename, int n, char *title, int useList)
11373 {
11374     FILE *f;
11375     char buf[MSG_SIZ];
11376
11377     if (strcmp(filename, "-") == 0) {
11378         f = stdin;
11379         title = "stdin";
11380     } else {
11381         f = fopen(filename, "rb");
11382         if (f == NULL) {
11383           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11384             DisplayError(buf, errno);
11385             return FALSE;
11386         }
11387     }
11388     if (fseek(f, 0, 0) == -1) {
11389         /* f is not seekable; probably a pipe */
11390         useList = FALSE;
11391     }
11392     if (useList && n == 0) {
11393         int error = GameListBuild(f);
11394         if (error) {
11395             DisplayError(_("Cannot build game list"), error);
11396         } else if (!ListEmpty(&gameList) &&
11397                    ((ListGame *) gameList.tailPred)->number > 1) {
11398             GameListPopUp(f, title);
11399             return TRUE;
11400         }
11401         GameListDestroy();
11402         n = 1;
11403     }
11404     if (n == 0) n = 1;
11405     return LoadGame(f, n, title, FALSE);
11406 }
11407
11408
11409 void
11410 MakeRegisteredMove ()
11411 {
11412     int fromX, fromY, toX, toY;
11413     char promoChar;
11414     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11415         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11416           case CMAIL_MOVE:
11417           case CMAIL_DRAW:
11418             if (appData.debugMode)
11419               fprintf(debugFP, "Restoring %s for game %d\n",
11420                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11421
11422             thinkOutput[0] = NULLCHAR;
11423             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11424             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11425             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11426             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11427             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11428             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11429             MakeMove(fromX, fromY, toX, toY, promoChar);
11430             ShowMove(fromX, fromY, toX, toY);
11431
11432             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11433               case MT_NONE:
11434               case MT_CHECK:
11435                 break;
11436
11437               case MT_CHECKMATE:
11438               case MT_STAINMATE:
11439                 if (WhiteOnMove(currentMove)) {
11440                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11441                 } else {
11442                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11443                 }
11444                 break;
11445
11446               case MT_STALEMATE:
11447                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11448                 break;
11449             }
11450
11451             break;
11452
11453           case CMAIL_RESIGN:
11454             if (WhiteOnMove(currentMove)) {
11455                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11456             } else {
11457                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11458             }
11459             break;
11460
11461           case CMAIL_ACCEPT:
11462             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11463             break;
11464
11465           default:
11466             break;
11467         }
11468     }
11469
11470     return;
11471 }
11472
11473 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11474 int
11475 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11476 {
11477     int retVal;
11478
11479     if (gameNumber > nCmailGames) {
11480         DisplayError(_("No more games in this message"), 0);
11481         return FALSE;
11482     }
11483     if (f == lastLoadGameFP) {
11484         int offset = gameNumber - lastLoadGameNumber;
11485         if (offset == 0) {
11486             cmailMsg[0] = NULLCHAR;
11487             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11488                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11489                 nCmailMovesRegistered--;
11490             }
11491             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11492             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11493                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11494             }
11495         } else {
11496             if (! RegisterMove()) return FALSE;
11497         }
11498     }
11499
11500     retVal = LoadGame(f, gameNumber, title, useList);
11501
11502     /* Make move registered during previous look at this game, if any */
11503     MakeRegisteredMove();
11504
11505     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11506         commentList[currentMove]
11507           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11508         DisplayComment(currentMove - 1, commentList[currentMove]);
11509     }
11510
11511     return retVal;
11512 }
11513
11514 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11515 int
11516 ReloadGame (int offset)
11517 {
11518     int gameNumber = lastLoadGameNumber + offset;
11519     if (lastLoadGameFP == NULL) {
11520         DisplayError(_("No game has been loaded yet"), 0);
11521         return FALSE;
11522     }
11523     if (gameNumber <= 0) {
11524         DisplayError(_("Can't back up any further"), 0);
11525         return FALSE;
11526     }
11527     if (cmailMsgLoaded) {
11528         return CmailLoadGame(lastLoadGameFP, gameNumber,
11529                              lastLoadGameTitle, lastLoadGameUseList);
11530     } else {
11531         return LoadGame(lastLoadGameFP, gameNumber,
11532                         lastLoadGameTitle, lastLoadGameUseList);
11533     }
11534 }
11535
11536 int keys[EmptySquare+1];
11537
11538 int
11539 PositionMatches (Board b1, Board b2)
11540 {
11541     int r, f, sum=0;
11542     switch(appData.searchMode) {
11543         case 1: return CompareWithRights(b1, b2);
11544         case 2:
11545             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11546                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11547             }
11548             return TRUE;
11549         case 3:
11550             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11551               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11552                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11553             }
11554             return sum==0;
11555         case 4:
11556             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11557                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11558             }
11559             return sum==0;
11560     }
11561     return TRUE;
11562 }
11563
11564 #define Q_PROMO  4
11565 #define Q_EP     3
11566 #define Q_BCASTL 2
11567 #define Q_WCASTL 1
11568
11569 int pieceList[256], quickBoard[256];
11570 ChessSquare pieceType[256] = { EmptySquare };
11571 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11572 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11573 int soughtTotal, turn;
11574 Boolean epOK, flipSearch;
11575
11576 typedef struct {
11577     unsigned char piece, to;
11578 } Move;
11579
11580 #define DSIZE (250000)
11581
11582 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11583 Move *moveDatabase = initialSpace;
11584 unsigned int movePtr, dataSize = DSIZE;
11585
11586 int
11587 MakePieceList (Board board, int *counts)
11588 {
11589     int r, f, n=Q_PROMO, total=0;
11590     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11591     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11592         int sq = f + (r<<4);
11593         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11594             quickBoard[sq] = ++n;
11595             pieceList[n] = sq;
11596             pieceType[n] = board[r][f];
11597             counts[board[r][f]]++;
11598             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11599             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11600             total++;
11601         }
11602     }
11603     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11604     return total;
11605 }
11606
11607 void
11608 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11609 {
11610     int sq = fromX + (fromY<<4);
11611     int piece = quickBoard[sq];
11612     quickBoard[sq] = 0;
11613     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11614     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11615         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11616         moveDatabase[movePtr++].piece = Q_WCASTL;
11617         quickBoard[sq] = piece;
11618         piece = quickBoard[from]; quickBoard[from] = 0;
11619         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11620     } else
11621     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11622         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11623         moveDatabase[movePtr++].piece = Q_BCASTL;
11624         quickBoard[sq] = piece;
11625         piece = quickBoard[from]; quickBoard[from] = 0;
11626         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11627     } else
11628     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11629         quickBoard[(fromY<<4)+toX] = 0;
11630         moveDatabase[movePtr].piece = Q_EP;
11631         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11632         moveDatabase[movePtr].to = sq;
11633     } else
11634     if(promoPiece != pieceType[piece]) {
11635         moveDatabase[movePtr++].piece = Q_PROMO;
11636         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11637     }
11638     moveDatabase[movePtr].piece = piece;
11639     quickBoard[sq] = piece;
11640     movePtr++;
11641 }
11642
11643 int
11644 PackGame (Board board)
11645 {
11646     Move *newSpace = NULL;
11647     moveDatabase[movePtr].piece = 0; // terminate previous game
11648     if(movePtr > dataSize) {
11649         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11650         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11651         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11652         if(newSpace) {
11653             int i;
11654             Move *p = moveDatabase, *q = newSpace;
11655             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11656             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11657             moveDatabase = newSpace;
11658         } else { // calloc failed, we must be out of memory. Too bad...
11659             dataSize = 0; // prevent calloc events for all subsequent games
11660             return 0;     // and signal this one isn't cached
11661         }
11662     }
11663     movePtr++;
11664     MakePieceList(board, counts);
11665     return movePtr;
11666 }
11667
11668 int
11669 QuickCompare (Board board, int *minCounts, int *maxCounts)
11670 {   // compare according to search mode
11671     int r, f;
11672     switch(appData.searchMode)
11673     {
11674       case 1: // exact position match
11675         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11676         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11677             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11678         }
11679         break;
11680       case 2: // can have extra material on empty squares
11681         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11682             if(board[r][f] == EmptySquare) continue;
11683             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11684         }
11685         break;
11686       case 3: // material with exact Pawn structure
11687         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11688             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11689             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11690         } // fall through to material comparison
11691       case 4: // exact material
11692         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11693         break;
11694       case 6: // material range with given imbalance
11695         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11696         // fall through to range comparison
11697       case 5: // material range
11698         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11699     }
11700     return TRUE;
11701 }
11702
11703 int
11704 QuickScan (Board board, Move *move)
11705 {   // reconstruct game,and compare all positions in it
11706     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11707     do {
11708         int piece = move->piece;
11709         int to = move->to, from = pieceList[piece];
11710         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11711           if(!piece) return -1;
11712           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11713             piece = (++move)->piece;
11714             from = pieceList[piece];
11715             counts[pieceType[piece]]--;
11716             pieceType[piece] = (ChessSquare) move->to;
11717             counts[move->to]++;
11718           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11719             counts[pieceType[quickBoard[to]]]--;
11720             quickBoard[to] = 0; total--;
11721             move++;
11722             continue;
11723           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11724             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11725             from  = pieceList[piece]; // so this must be King
11726             quickBoard[from] = 0;
11727             pieceList[piece] = to;
11728             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11729             quickBoard[from] = 0; // rook
11730             quickBoard[to] = piece;
11731             to = move->to; piece = move->piece;
11732             goto aftercastle;
11733           }
11734         }
11735         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11736         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11737         quickBoard[from] = 0;
11738       aftercastle:
11739         quickBoard[to] = piece;
11740         pieceList[piece] = to;
11741         cnt++; turn ^= 3;
11742         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11743            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11744            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11745                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11746           ) {
11747             static int lastCounts[EmptySquare+1];
11748             int i;
11749             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11750             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11751         } else stretch = 0;
11752         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11753         move++;
11754     } while(1);
11755 }
11756
11757 void
11758 InitSearch ()
11759 {
11760     int r, f;
11761     flipSearch = FALSE;
11762     CopyBoard(soughtBoard, boards[currentMove]);
11763     soughtTotal = MakePieceList(soughtBoard, maxSought);
11764     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11765     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11766     CopyBoard(reverseBoard, boards[currentMove]);
11767     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11768         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11769         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11770         reverseBoard[r][f] = piece;
11771     }
11772     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11773     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11774     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11775                  || (boards[currentMove][CASTLING][2] == NoRights || 
11776                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11777                  && (boards[currentMove][CASTLING][5] == NoRights || 
11778                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11779       ) {
11780         flipSearch = TRUE;
11781         CopyBoard(flipBoard, soughtBoard);
11782         CopyBoard(rotateBoard, reverseBoard);
11783         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11784             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11785             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11786         }
11787     }
11788     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11789     if(appData.searchMode >= 5) {
11790         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11791         MakePieceList(soughtBoard, minSought);
11792         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11793     }
11794     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11795         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11796 }
11797
11798 GameInfo dummyInfo;
11799 static int creatingBook;
11800
11801 int
11802 GameContainsPosition (FILE *f, ListGame *lg)
11803 {
11804     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11805     int fromX, fromY, toX, toY;
11806     char promoChar;
11807     static int initDone=FALSE;
11808
11809     // weed out games based on numerical tag comparison
11810     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11811     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11812     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11813     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11814     if(!initDone) {
11815         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11816         initDone = TRUE;
11817     }
11818     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11819     else CopyBoard(boards[scratch], initialPosition); // default start position
11820     if(lg->moves) {
11821         turn = btm + 1;
11822         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11823         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11824     }
11825     if(btm) plyNr++;
11826     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11827     fseek(f, lg->offset, 0);
11828     yynewfile(f);
11829     while(1) {
11830         yyboardindex = scratch;
11831         quickFlag = plyNr+1;
11832         next = Myylex();
11833         quickFlag = 0;
11834         switch(next) {
11835             case PGNTag:
11836                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11837             default:
11838                 continue;
11839
11840             case XBoardGame:
11841             case GNUChessGame:
11842                 if(plyNr) return -1; // after we have seen moves, this is for new game
11843               continue;
11844
11845             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11846             case ImpossibleMove:
11847             case WhiteWins: // game ends here with these four
11848             case BlackWins:
11849             case GameIsDrawn:
11850             case GameUnfinished:
11851                 return -1;
11852
11853             case IllegalMove:
11854                 if(appData.testLegality) return -1;
11855             case WhiteCapturesEnPassant:
11856             case BlackCapturesEnPassant:
11857             case WhitePromotion:
11858             case BlackPromotion:
11859             case WhiteNonPromotion:
11860             case BlackNonPromotion:
11861             case NormalMove:
11862             case WhiteKingSideCastle:
11863             case WhiteQueenSideCastle:
11864             case BlackKingSideCastle:
11865             case BlackQueenSideCastle:
11866             case WhiteKingSideCastleWild:
11867             case WhiteQueenSideCastleWild:
11868             case BlackKingSideCastleWild:
11869             case BlackQueenSideCastleWild:
11870             case WhiteHSideCastleFR:
11871             case WhiteASideCastleFR:
11872             case BlackHSideCastleFR:
11873             case BlackASideCastleFR:
11874                 fromX = currentMoveString[0] - AAA;
11875                 fromY = currentMoveString[1] - ONE;
11876                 toX = currentMoveString[2] - AAA;
11877                 toY = currentMoveString[3] - ONE;
11878                 promoChar = currentMoveString[4];
11879                 break;
11880             case WhiteDrop:
11881             case BlackDrop:
11882                 fromX = next == WhiteDrop ?
11883                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11884                   (int) CharToPiece(ToLower(currentMoveString[0]));
11885                 fromY = DROP_RANK;
11886                 toX = currentMoveString[2] - AAA;
11887                 toY = currentMoveString[3] - ONE;
11888                 promoChar = 0;
11889                 break;
11890         }
11891         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11892         plyNr++;
11893         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11894         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11895         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11896         if(appData.findMirror) {
11897             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11898             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11899         }
11900     }
11901 }
11902
11903 /* Load the nth game from open file f */
11904 int
11905 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11906 {
11907     ChessMove cm;
11908     char buf[MSG_SIZ];
11909     int gn = gameNumber;
11910     ListGame *lg = NULL;
11911     int numPGNTags = 0;
11912     int err, pos = -1;
11913     GameMode oldGameMode;
11914     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11915
11916     if (appData.debugMode)
11917         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11918
11919     if (gameMode == Training )
11920         SetTrainingModeOff();
11921
11922     oldGameMode = gameMode;
11923     if (gameMode != BeginningOfGame) {
11924       Reset(FALSE, TRUE);
11925     }
11926
11927     gameFileFP = f;
11928     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11929         fclose(lastLoadGameFP);
11930     }
11931
11932     if (useList) {
11933         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11934
11935         if (lg) {
11936             fseek(f, lg->offset, 0);
11937             GameListHighlight(gameNumber);
11938             pos = lg->position;
11939             gn = 1;
11940         }
11941         else {
11942             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11943               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11944             else
11945             DisplayError(_("Game number out of range"), 0);
11946             return FALSE;
11947         }
11948     } else {
11949         GameListDestroy();
11950         if (fseek(f, 0, 0) == -1) {
11951             if (f == lastLoadGameFP ?
11952                 gameNumber == lastLoadGameNumber + 1 :
11953                 gameNumber == 1) {
11954                 gn = 1;
11955             } else {
11956                 DisplayError(_("Can't seek on game file"), 0);
11957                 return FALSE;
11958             }
11959         }
11960     }
11961     lastLoadGameFP = f;
11962     lastLoadGameNumber = gameNumber;
11963     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11964     lastLoadGameUseList = useList;
11965
11966     yynewfile(f);
11967
11968     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11969       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11970                 lg->gameInfo.black);
11971             DisplayTitle(buf);
11972     } else if (*title != NULLCHAR) {
11973         if (gameNumber > 1) {
11974           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11975             DisplayTitle(buf);
11976         } else {
11977             DisplayTitle(title);
11978         }
11979     }
11980
11981     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11982         gameMode = PlayFromGameFile;
11983         ModeHighlight();
11984     }
11985
11986     currentMove = forwardMostMove = backwardMostMove = 0;
11987     CopyBoard(boards[0], initialPosition);
11988     StopClocks();
11989
11990     /*
11991      * Skip the first gn-1 games in the file.
11992      * Also skip over anything that precedes an identifiable
11993      * start of game marker, to avoid being confused by
11994      * garbage at the start of the file.  Currently
11995      * recognized start of game markers are the move number "1",
11996      * the pattern "gnuchess .* game", the pattern
11997      * "^[#;%] [^ ]* game file", and a PGN tag block.
11998      * A game that starts with one of the latter two patterns
11999      * will also have a move number 1, possibly
12000      * following a position diagram.
12001      * 5-4-02: Let's try being more lenient and allowing a game to
12002      * start with an unnumbered move.  Does that break anything?
12003      */
12004     cm = lastLoadGameStart = EndOfFile;
12005     while (gn > 0) {
12006         yyboardindex = forwardMostMove;
12007         cm = (ChessMove) Myylex();
12008         switch (cm) {
12009           case EndOfFile:
12010             if (cmailMsgLoaded) {
12011                 nCmailGames = CMAIL_MAX_GAMES - gn;
12012             } else {
12013                 Reset(TRUE, TRUE);
12014                 DisplayError(_("Game not found in file"), 0);
12015             }
12016             return FALSE;
12017
12018           case GNUChessGame:
12019           case XBoardGame:
12020             gn--;
12021             lastLoadGameStart = cm;
12022             break;
12023
12024           case MoveNumberOne:
12025             switch (lastLoadGameStart) {
12026               case GNUChessGame:
12027               case XBoardGame:
12028               case PGNTag:
12029                 break;
12030               case MoveNumberOne:
12031               case EndOfFile:
12032                 gn--;           /* count this game */
12033                 lastLoadGameStart = cm;
12034                 break;
12035               default:
12036                 /* impossible */
12037                 break;
12038             }
12039             break;
12040
12041           case PGNTag:
12042             switch (lastLoadGameStart) {
12043               case GNUChessGame:
12044               case PGNTag:
12045               case MoveNumberOne:
12046               case EndOfFile:
12047                 gn--;           /* count this game */
12048                 lastLoadGameStart = cm;
12049                 break;
12050               case XBoardGame:
12051                 lastLoadGameStart = cm; /* game counted already */
12052                 break;
12053               default:
12054                 /* impossible */
12055                 break;
12056             }
12057             if (gn > 0) {
12058                 do {
12059                     yyboardindex = forwardMostMove;
12060                     cm = (ChessMove) Myylex();
12061                 } while (cm == PGNTag || cm == Comment);
12062             }
12063             break;
12064
12065           case WhiteWins:
12066           case BlackWins:
12067           case GameIsDrawn:
12068             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12069                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12070                     != CMAIL_OLD_RESULT) {
12071                     nCmailResults ++ ;
12072                     cmailResult[  CMAIL_MAX_GAMES
12073                                 - gn - 1] = CMAIL_OLD_RESULT;
12074                 }
12075             }
12076             break;
12077
12078           case NormalMove:
12079             /* Only a NormalMove can be at the start of a game
12080              * without a position diagram. */
12081             if (lastLoadGameStart == EndOfFile ) {
12082               gn--;
12083               lastLoadGameStart = MoveNumberOne;
12084             }
12085             break;
12086
12087           default:
12088             break;
12089         }
12090     }
12091
12092     if (appData.debugMode)
12093       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12094
12095     if (cm == XBoardGame) {
12096         /* Skip any header junk before position diagram and/or move 1 */
12097         for (;;) {
12098             yyboardindex = forwardMostMove;
12099             cm = (ChessMove) Myylex();
12100
12101             if (cm == EndOfFile ||
12102                 cm == GNUChessGame || cm == XBoardGame) {
12103                 /* Empty game; pretend end-of-file and handle later */
12104                 cm = EndOfFile;
12105                 break;
12106             }
12107
12108             if (cm == MoveNumberOne || cm == PositionDiagram ||
12109                 cm == PGNTag || cm == Comment)
12110               break;
12111         }
12112     } else if (cm == GNUChessGame) {
12113         if (gameInfo.event != NULL) {
12114             free(gameInfo.event);
12115         }
12116         gameInfo.event = StrSave(yy_text);
12117     }
12118
12119     startedFromSetupPosition = FALSE;
12120     while (cm == PGNTag) {
12121         if (appData.debugMode)
12122           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12123         err = ParsePGNTag(yy_text, &gameInfo);
12124         if (!err) numPGNTags++;
12125
12126         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12127         if(gameInfo.variant != oldVariant) {
12128             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12129             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12130             InitPosition(TRUE);
12131             oldVariant = gameInfo.variant;
12132             if (appData.debugMode)
12133               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12134         }
12135
12136
12137         if (gameInfo.fen != NULL) {
12138           Board initial_position;
12139           startedFromSetupPosition = TRUE;
12140           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12141             Reset(TRUE, TRUE);
12142             DisplayError(_("Bad FEN position in file"), 0);
12143             return FALSE;
12144           }
12145           CopyBoard(boards[0], initial_position);
12146           if (blackPlaysFirst) {
12147             currentMove = forwardMostMove = backwardMostMove = 1;
12148             CopyBoard(boards[1], initial_position);
12149             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12150             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12151             timeRemaining[0][1] = whiteTimeRemaining;
12152             timeRemaining[1][1] = blackTimeRemaining;
12153             if (commentList[0] != NULL) {
12154               commentList[1] = commentList[0];
12155               commentList[0] = NULL;
12156             }
12157           } else {
12158             currentMove = forwardMostMove = backwardMostMove = 0;
12159           }
12160           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12161           {   int i;
12162               initialRulePlies = FENrulePlies;
12163               for( i=0; i< nrCastlingRights; i++ )
12164                   initialRights[i] = initial_position[CASTLING][i];
12165           }
12166           yyboardindex = forwardMostMove;
12167           free(gameInfo.fen);
12168           gameInfo.fen = NULL;
12169         }
12170
12171         yyboardindex = forwardMostMove;
12172         cm = (ChessMove) Myylex();
12173
12174         /* Handle comments interspersed among the tags */
12175         while (cm == Comment) {
12176             char *p;
12177             if (appData.debugMode)
12178               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12179             p = yy_text;
12180             AppendComment(currentMove, p, FALSE);
12181             yyboardindex = forwardMostMove;
12182             cm = (ChessMove) Myylex();
12183         }
12184     }
12185
12186     /* don't rely on existence of Event tag since if game was
12187      * pasted from clipboard the Event tag may not exist
12188      */
12189     if (numPGNTags > 0){
12190         char *tags;
12191         if (gameInfo.variant == VariantNormal) {
12192           VariantClass v = StringToVariant(gameInfo.event);
12193           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12194           if(v < VariantShogi) gameInfo.variant = v;
12195         }
12196         if (!matchMode) {
12197           if( appData.autoDisplayTags ) {
12198             tags = PGNTags(&gameInfo);
12199             TagsPopUp(tags, CmailMsg());
12200             free(tags);
12201           }
12202         }
12203     } else {
12204         /* Make something up, but don't display it now */
12205         SetGameInfo();
12206         TagsPopDown();
12207     }
12208
12209     if (cm == PositionDiagram) {
12210         int i, j;
12211         char *p;
12212         Board initial_position;
12213
12214         if (appData.debugMode)
12215           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12216
12217         if (!startedFromSetupPosition) {
12218             p = yy_text;
12219             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12220               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12221                 switch (*p) {
12222                   case '{':
12223                   case '[':
12224                   case '-':
12225                   case ' ':
12226                   case '\t':
12227                   case '\n':
12228                   case '\r':
12229                     break;
12230                   default:
12231                     initial_position[i][j++] = CharToPiece(*p);
12232                     break;
12233                 }
12234             while (*p == ' ' || *p == '\t' ||
12235                    *p == '\n' || *p == '\r') p++;
12236
12237             if (strncmp(p, "black", strlen("black"))==0)
12238               blackPlaysFirst = TRUE;
12239             else
12240               blackPlaysFirst = FALSE;
12241             startedFromSetupPosition = TRUE;
12242
12243             CopyBoard(boards[0], initial_position);
12244             if (blackPlaysFirst) {
12245                 currentMove = forwardMostMove = backwardMostMove = 1;
12246                 CopyBoard(boards[1], initial_position);
12247                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12248                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12249                 timeRemaining[0][1] = whiteTimeRemaining;
12250                 timeRemaining[1][1] = blackTimeRemaining;
12251                 if (commentList[0] != NULL) {
12252                     commentList[1] = commentList[0];
12253                     commentList[0] = NULL;
12254                 }
12255             } else {
12256                 currentMove = forwardMostMove = backwardMostMove = 0;
12257             }
12258         }
12259         yyboardindex = forwardMostMove;
12260         cm = (ChessMove) Myylex();
12261     }
12262
12263   if(!creatingBook) {
12264     if (first.pr == NoProc) {
12265         StartChessProgram(&first);
12266     }
12267     InitChessProgram(&first, FALSE);
12268     SendToProgram("force\n", &first);
12269     if (startedFromSetupPosition) {
12270         SendBoard(&first, forwardMostMove);
12271     if (appData.debugMode) {
12272         fprintf(debugFP, "Load Game\n");
12273     }
12274         DisplayBothClocks();
12275     }
12276   }
12277
12278     /* [HGM] server: flag to write setup moves in broadcast file as one */
12279     loadFlag = appData.suppressLoadMoves;
12280
12281     while (cm == Comment) {
12282         char *p;
12283         if (appData.debugMode)
12284           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12285         p = yy_text;
12286         AppendComment(currentMove, p, FALSE);
12287         yyboardindex = forwardMostMove;
12288         cm = (ChessMove) Myylex();
12289     }
12290
12291     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12292         cm == WhiteWins || cm == BlackWins ||
12293         cm == GameIsDrawn || cm == GameUnfinished) {
12294         DisplayMessage("", _("No moves in game"));
12295         if (cmailMsgLoaded) {
12296             if (appData.debugMode)
12297               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12298             ClearHighlights();
12299             flipView = FALSE;
12300         }
12301         DrawPosition(FALSE, boards[currentMove]);
12302         DisplayBothClocks();
12303         gameMode = EditGame;
12304         ModeHighlight();
12305         gameFileFP = NULL;
12306         cmailOldMove = 0;
12307         return TRUE;
12308     }
12309
12310     // [HGM] PV info: routine tests if comment empty
12311     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12312         DisplayComment(currentMove - 1, commentList[currentMove]);
12313     }
12314     if (!matchMode && appData.timeDelay != 0)
12315       DrawPosition(FALSE, boards[currentMove]);
12316
12317     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12318       programStats.ok_to_send = 1;
12319     }
12320
12321     /* if the first token after the PGN tags is a move
12322      * and not move number 1, retrieve it from the parser
12323      */
12324     if (cm != MoveNumberOne)
12325         LoadGameOneMove(cm);
12326
12327     /* load the remaining moves from the file */
12328     while (LoadGameOneMove(EndOfFile)) {
12329       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12330       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12331     }
12332
12333     /* rewind to the start of the game */
12334     currentMove = backwardMostMove;
12335
12336     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12337
12338     if (oldGameMode == AnalyzeFile ||
12339         oldGameMode == AnalyzeMode) {
12340       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12341       keepInfo = 1;
12342       AnalyzeFileEvent();
12343       keepInfo = 0;
12344     }
12345
12346     if(creatingBook) return TRUE;
12347     if (!matchMode && pos > 0) {
12348         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12349     } else
12350     if (matchMode || appData.timeDelay == 0) {
12351       ToEndEvent();
12352     } else if (appData.timeDelay > 0) {
12353       AutoPlayGameLoop();
12354     }
12355
12356     if (appData.debugMode)
12357         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12358
12359     loadFlag = 0; /* [HGM] true game starts */
12360     return TRUE;
12361 }
12362
12363 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12364 int
12365 ReloadPosition (int offset)
12366 {
12367     int positionNumber = lastLoadPositionNumber + offset;
12368     if (lastLoadPositionFP == NULL) {
12369         DisplayError(_("No position has been loaded yet"), 0);
12370         return FALSE;
12371     }
12372     if (positionNumber <= 0) {
12373         DisplayError(_("Can't back up any further"), 0);
12374         return FALSE;
12375     }
12376     return LoadPosition(lastLoadPositionFP, positionNumber,
12377                         lastLoadPositionTitle);
12378 }
12379
12380 /* Load the nth position from the given file */
12381 int
12382 LoadPositionFromFile (char *filename, int n, char *title)
12383 {
12384     FILE *f;
12385     char buf[MSG_SIZ];
12386
12387     if (strcmp(filename, "-") == 0) {
12388         return LoadPosition(stdin, n, "stdin");
12389     } else {
12390         f = fopen(filename, "rb");
12391         if (f == NULL) {
12392             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12393             DisplayError(buf, errno);
12394             return FALSE;
12395         } else {
12396             return LoadPosition(f, n, title);
12397         }
12398     }
12399 }
12400
12401 /* Load the nth position from the given open file, and close it */
12402 int
12403 LoadPosition (FILE *f, int positionNumber, char *title)
12404 {
12405     char *p, line[MSG_SIZ];
12406     Board initial_position;
12407     int i, j, fenMode, pn;
12408
12409     if (gameMode == Training )
12410         SetTrainingModeOff();
12411
12412     if (gameMode != BeginningOfGame) {
12413         Reset(FALSE, TRUE);
12414     }
12415     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12416         fclose(lastLoadPositionFP);
12417     }
12418     if (positionNumber == 0) positionNumber = 1;
12419     lastLoadPositionFP = f;
12420     lastLoadPositionNumber = positionNumber;
12421     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12422     if (first.pr == NoProc && !appData.noChessProgram) {
12423       StartChessProgram(&first);
12424       InitChessProgram(&first, FALSE);
12425     }
12426     pn = positionNumber;
12427     if (positionNumber < 0) {
12428         /* Negative position number means to seek to that byte offset */
12429         if (fseek(f, -positionNumber, 0) == -1) {
12430             DisplayError(_("Can't seek on position file"), 0);
12431             return FALSE;
12432         };
12433         pn = 1;
12434     } else {
12435         if (fseek(f, 0, 0) == -1) {
12436             if (f == lastLoadPositionFP ?
12437                 positionNumber == lastLoadPositionNumber + 1 :
12438                 positionNumber == 1) {
12439                 pn = 1;
12440             } else {
12441                 DisplayError(_("Can't seek on position file"), 0);
12442                 return FALSE;
12443             }
12444         }
12445     }
12446     /* See if this file is FEN or old-style xboard */
12447     if (fgets(line, MSG_SIZ, f) == NULL) {
12448         DisplayError(_("Position not found in file"), 0);
12449         return FALSE;
12450     }
12451     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12452     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12453
12454     if (pn >= 2) {
12455         if (fenMode || line[0] == '#') pn--;
12456         while (pn > 0) {
12457             /* skip positions before number pn */
12458             if (fgets(line, MSG_SIZ, f) == NULL) {
12459                 Reset(TRUE, TRUE);
12460                 DisplayError(_("Position not found in file"), 0);
12461                 return FALSE;
12462             }
12463             if (fenMode || line[0] == '#') pn--;
12464         }
12465     }
12466
12467     if (fenMode) {
12468         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12469             DisplayError(_("Bad FEN position in file"), 0);
12470             return FALSE;
12471         }
12472     } else {
12473         (void) fgets(line, MSG_SIZ, f);
12474         (void) fgets(line, MSG_SIZ, f);
12475
12476         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12477             (void) fgets(line, MSG_SIZ, f);
12478             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12479                 if (*p == ' ')
12480                   continue;
12481                 initial_position[i][j++] = CharToPiece(*p);
12482             }
12483         }
12484
12485         blackPlaysFirst = FALSE;
12486         if (!feof(f)) {
12487             (void) fgets(line, MSG_SIZ, f);
12488             if (strncmp(line, "black", strlen("black"))==0)
12489               blackPlaysFirst = TRUE;
12490         }
12491     }
12492     startedFromSetupPosition = TRUE;
12493
12494     CopyBoard(boards[0], initial_position);
12495     if (blackPlaysFirst) {
12496         currentMove = forwardMostMove = backwardMostMove = 1;
12497         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12498         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12499         CopyBoard(boards[1], initial_position);
12500         DisplayMessage("", _("Black to play"));
12501     } else {
12502         currentMove = forwardMostMove = backwardMostMove = 0;
12503         DisplayMessage("", _("White to play"));
12504     }
12505     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12506     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12507         SendToProgram("force\n", &first);
12508         SendBoard(&first, forwardMostMove);
12509     }
12510     if (appData.debugMode) {
12511 int i, j;
12512   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12513   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12514         fprintf(debugFP, "Load Position\n");
12515     }
12516
12517     if (positionNumber > 1) {
12518       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12519         DisplayTitle(line);
12520     } else {
12521         DisplayTitle(title);
12522     }
12523     gameMode = EditGame;
12524     ModeHighlight();
12525     ResetClocks();
12526     timeRemaining[0][1] = whiteTimeRemaining;
12527     timeRemaining[1][1] = blackTimeRemaining;
12528     DrawPosition(FALSE, boards[currentMove]);
12529
12530     return TRUE;
12531 }
12532
12533
12534 void
12535 CopyPlayerNameIntoFileName (char **dest, char *src)
12536 {
12537     while (*src != NULLCHAR && *src != ',') {
12538         if (*src == ' ') {
12539             *(*dest)++ = '_';
12540             src++;
12541         } else {
12542             *(*dest)++ = *src++;
12543         }
12544     }
12545 }
12546
12547 char *
12548 DefaultFileName (char *ext)
12549 {
12550     static char def[MSG_SIZ];
12551     char *p;
12552
12553     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12554         p = def;
12555         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12556         *p++ = '-';
12557         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12558         *p++ = '.';
12559         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12560     } else {
12561         def[0] = NULLCHAR;
12562     }
12563     return def;
12564 }
12565
12566 /* Save the current game to the given file */
12567 int
12568 SaveGameToFile (char *filename, int append)
12569 {
12570     FILE *f;
12571     char buf[MSG_SIZ];
12572     int result, i, t,tot=0;
12573
12574     if (strcmp(filename, "-") == 0) {
12575         return SaveGame(stdout, 0, NULL);
12576     } else {
12577         for(i=0; i<10; i++) { // upto 10 tries
12578              f = fopen(filename, append ? "a" : "w");
12579              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12580              if(f || errno != 13) break;
12581              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12582              tot += t;
12583         }
12584         if (f == NULL) {
12585             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12586             DisplayError(buf, errno);
12587             return FALSE;
12588         } else {
12589             safeStrCpy(buf, lastMsg, MSG_SIZ);
12590             DisplayMessage(_("Waiting for access to save file"), "");
12591             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12592             DisplayMessage(_("Saving game"), "");
12593             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12594             result = SaveGame(f, 0, NULL);
12595             DisplayMessage(buf, "");
12596             return result;
12597         }
12598     }
12599 }
12600
12601 char *
12602 SavePart (char *str)
12603 {
12604     static char buf[MSG_SIZ];
12605     char *p;
12606
12607     p = strchr(str, ' ');
12608     if (p == NULL) return str;
12609     strncpy(buf, str, p - str);
12610     buf[p - str] = NULLCHAR;
12611     return buf;
12612 }
12613
12614 #define PGN_MAX_LINE 75
12615
12616 #define PGN_SIDE_WHITE  0
12617 #define PGN_SIDE_BLACK  1
12618
12619 static int
12620 FindFirstMoveOutOfBook (int side)
12621 {
12622     int result = -1;
12623
12624     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12625         int index = backwardMostMove;
12626         int has_book_hit = 0;
12627
12628         if( (index % 2) != side ) {
12629             index++;
12630         }
12631
12632         while( index < forwardMostMove ) {
12633             /* Check to see if engine is in book */
12634             int depth = pvInfoList[index].depth;
12635             int score = pvInfoList[index].score;
12636             int in_book = 0;
12637
12638             if( depth <= 2 ) {
12639                 in_book = 1;
12640             }
12641             else if( score == 0 && depth == 63 ) {
12642                 in_book = 1; /* Zappa */
12643             }
12644             else if( score == 2 && depth == 99 ) {
12645                 in_book = 1; /* Abrok */
12646             }
12647
12648             has_book_hit += in_book;
12649
12650             if( ! in_book ) {
12651                 result = index;
12652
12653                 break;
12654             }
12655
12656             index += 2;
12657         }
12658     }
12659
12660     return result;
12661 }
12662
12663 void
12664 GetOutOfBookInfo (char * buf)
12665 {
12666     int oob[2];
12667     int i;
12668     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12669
12670     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12671     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12672
12673     *buf = '\0';
12674
12675     if( oob[0] >= 0 || oob[1] >= 0 ) {
12676         for( i=0; i<2; i++ ) {
12677             int idx = oob[i];
12678
12679             if( idx >= 0 ) {
12680                 if( i > 0 && oob[0] >= 0 ) {
12681                     strcat( buf, "   " );
12682                 }
12683
12684                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12685                 sprintf( buf+strlen(buf), "%s%.2f",
12686                     pvInfoList[idx].score >= 0 ? "+" : "",
12687                     pvInfoList[idx].score / 100.0 );
12688             }
12689         }
12690     }
12691 }
12692
12693 /* Save game in PGN style and close the file */
12694 int
12695 SaveGamePGN (FILE *f)
12696 {
12697     int i, offset, linelen, newblock;
12698 //    char *movetext;
12699     char numtext[32];
12700     int movelen, numlen, blank;
12701     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12702
12703     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12704
12705     PrintPGNTags(f, &gameInfo);
12706
12707     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12708
12709     if (backwardMostMove > 0 || startedFromSetupPosition) {
12710         char *fen = PositionToFEN(backwardMostMove, NULL);
12711         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12712         fprintf(f, "\n{--------------\n");
12713         PrintPosition(f, backwardMostMove);
12714         fprintf(f, "--------------}\n");
12715         free(fen);
12716     }
12717     else {
12718         /* [AS] Out of book annotation */
12719         if( appData.saveOutOfBookInfo ) {
12720             char buf[64];
12721
12722             GetOutOfBookInfo( buf );
12723
12724             if( buf[0] != '\0' ) {
12725                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12726             }
12727         }
12728
12729         fprintf(f, "\n");
12730     }
12731
12732     i = backwardMostMove;
12733     linelen = 0;
12734     newblock = TRUE;
12735
12736     while (i < forwardMostMove) {
12737         /* Print comments preceding this move */
12738         if (commentList[i] != NULL) {
12739             if (linelen > 0) fprintf(f, "\n");
12740             fprintf(f, "%s", commentList[i]);
12741             linelen = 0;
12742             newblock = TRUE;
12743         }
12744
12745         /* Format move number */
12746         if ((i % 2) == 0)
12747           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12748         else
12749           if (newblock)
12750             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12751           else
12752             numtext[0] = NULLCHAR;
12753
12754         numlen = strlen(numtext);
12755         newblock = FALSE;
12756
12757         /* Print move number */
12758         blank = linelen > 0 && numlen > 0;
12759         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12760             fprintf(f, "\n");
12761             linelen = 0;
12762             blank = 0;
12763         }
12764         if (blank) {
12765             fprintf(f, " ");
12766             linelen++;
12767         }
12768         fprintf(f, "%s", numtext);
12769         linelen += numlen;
12770
12771         /* Get move */
12772         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12773         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12774
12775         /* Print move */
12776         blank = linelen > 0 && movelen > 0;
12777         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12778             fprintf(f, "\n");
12779             linelen = 0;
12780             blank = 0;
12781         }
12782         if (blank) {
12783             fprintf(f, " ");
12784             linelen++;
12785         }
12786         fprintf(f, "%s", move_buffer);
12787         linelen += movelen;
12788
12789         /* [AS] Add PV info if present */
12790         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12791             /* [HGM] add time */
12792             char buf[MSG_SIZ]; int seconds;
12793
12794             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12795
12796             if( seconds <= 0)
12797               buf[0] = 0;
12798             else
12799               if( seconds < 30 )
12800                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12801               else
12802                 {
12803                   seconds = (seconds + 4)/10; // round to full seconds
12804                   if( seconds < 60 )
12805                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12806                   else
12807                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12808                 }
12809
12810             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12811                       pvInfoList[i].score >= 0 ? "+" : "",
12812                       pvInfoList[i].score / 100.0,
12813                       pvInfoList[i].depth,
12814                       buf );
12815
12816             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12817
12818             /* Print score/depth */
12819             blank = linelen > 0 && movelen > 0;
12820             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12821                 fprintf(f, "\n");
12822                 linelen = 0;
12823                 blank = 0;
12824             }
12825             if (blank) {
12826                 fprintf(f, " ");
12827                 linelen++;
12828             }
12829             fprintf(f, "%s", move_buffer);
12830             linelen += movelen;
12831         }
12832
12833         i++;
12834     }
12835
12836     /* Start a new line */
12837     if (linelen > 0) fprintf(f, "\n");
12838
12839     /* Print comments after last move */
12840     if (commentList[i] != NULL) {
12841         fprintf(f, "%s\n", commentList[i]);
12842     }
12843
12844     /* Print result */
12845     if (gameInfo.resultDetails != NULL &&
12846         gameInfo.resultDetails[0] != NULLCHAR) {
12847         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12848                 PGNResult(gameInfo.result));
12849     } else {
12850         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12851     }
12852
12853     fclose(f);
12854     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12855     return TRUE;
12856 }
12857
12858 /* Save game in old style and close the file */
12859 int
12860 SaveGameOldStyle (FILE *f)
12861 {
12862     int i, offset;
12863     time_t tm;
12864
12865     tm = time((time_t *) NULL);
12866
12867     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12868     PrintOpponents(f);
12869
12870     if (backwardMostMove > 0 || startedFromSetupPosition) {
12871         fprintf(f, "\n[--------------\n");
12872         PrintPosition(f, backwardMostMove);
12873         fprintf(f, "--------------]\n");
12874     } else {
12875         fprintf(f, "\n");
12876     }
12877
12878     i = backwardMostMove;
12879     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12880
12881     while (i < forwardMostMove) {
12882         if (commentList[i] != NULL) {
12883             fprintf(f, "[%s]\n", commentList[i]);
12884         }
12885
12886         if ((i % 2) == 1) {
12887             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12888             i++;
12889         } else {
12890             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12891             i++;
12892             if (commentList[i] != NULL) {
12893                 fprintf(f, "\n");
12894                 continue;
12895             }
12896             if (i >= forwardMostMove) {
12897                 fprintf(f, "\n");
12898                 break;
12899             }
12900             fprintf(f, "%s\n", parseList[i]);
12901             i++;
12902         }
12903     }
12904
12905     if (commentList[i] != NULL) {
12906         fprintf(f, "[%s]\n", commentList[i]);
12907     }
12908
12909     /* This isn't really the old style, but it's close enough */
12910     if (gameInfo.resultDetails != NULL &&
12911         gameInfo.resultDetails[0] != NULLCHAR) {
12912         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12913                 gameInfo.resultDetails);
12914     } else {
12915         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12916     }
12917
12918     fclose(f);
12919     return TRUE;
12920 }
12921
12922 /* Save the current game to open file f and close the file */
12923 int
12924 SaveGame (FILE *f, int dummy, char *dummy2)
12925 {
12926     if (gameMode == EditPosition) EditPositionDone(TRUE);
12927     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12928     if (appData.oldSaveStyle)
12929       return SaveGameOldStyle(f);
12930     else
12931       return SaveGamePGN(f);
12932 }
12933
12934 /* Save the current position to the given file */
12935 int
12936 SavePositionToFile (char *filename)
12937 {
12938     FILE *f;
12939     char buf[MSG_SIZ];
12940
12941     if (strcmp(filename, "-") == 0) {
12942         return SavePosition(stdout, 0, NULL);
12943     } else {
12944         f = fopen(filename, "a");
12945         if (f == NULL) {
12946             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12947             DisplayError(buf, errno);
12948             return FALSE;
12949         } else {
12950             safeStrCpy(buf, lastMsg, MSG_SIZ);
12951             DisplayMessage(_("Waiting for access to save file"), "");
12952             flock(fileno(f), LOCK_EX); // [HGM] lock
12953             DisplayMessage(_("Saving position"), "");
12954             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12955             SavePosition(f, 0, NULL);
12956             DisplayMessage(buf, "");
12957             return TRUE;
12958         }
12959     }
12960 }
12961
12962 /* Save the current position to the given open file and close the file */
12963 int
12964 SavePosition (FILE *f, int dummy, char *dummy2)
12965 {
12966     time_t tm;
12967     char *fen;
12968
12969     if (gameMode == EditPosition) EditPositionDone(TRUE);
12970     if (appData.oldSaveStyle) {
12971         tm = time((time_t *) NULL);
12972
12973         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12974         PrintOpponents(f);
12975         fprintf(f, "[--------------\n");
12976         PrintPosition(f, currentMove);
12977         fprintf(f, "--------------]\n");
12978     } else {
12979         fen = PositionToFEN(currentMove, NULL);
12980         fprintf(f, "%s\n", fen);
12981         free(fen);
12982     }
12983     fclose(f);
12984     return TRUE;
12985 }
12986
12987 void
12988 ReloadCmailMsgEvent (int unregister)
12989 {
12990 #if !WIN32
12991     static char *inFilename = NULL;
12992     static char *outFilename;
12993     int i;
12994     struct stat inbuf, outbuf;
12995     int status;
12996
12997     /* Any registered moves are unregistered if unregister is set, */
12998     /* i.e. invoked by the signal handler */
12999     if (unregister) {
13000         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13001             cmailMoveRegistered[i] = FALSE;
13002             if (cmailCommentList[i] != NULL) {
13003                 free(cmailCommentList[i]);
13004                 cmailCommentList[i] = NULL;
13005             }
13006         }
13007         nCmailMovesRegistered = 0;
13008     }
13009
13010     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13011         cmailResult[i] = CMAIL_NOT_RESULT;
13012     }
13013     nCmailResults = 0;
13014
13015     if (inFilename == NULL) {
13016         /* Because the filenames are static they only get malloced once  */
13017         /* and they never get freed                                      */
13018         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13019         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13020
13021         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13022         sprintf(outFilename, "%s.out", appData.cmailGameName);
13023     }
13024
13025     status = stat(outFilename, &outbuf);
13026     if (status < 0) {
13027         cmailMailedMove = FALSE;
13028     } else {
13029         status = stat(inFilename, &inbuf);
13030         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13031     }
13032
13033     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13034        counts the games, notes how each one terminated, etc.
13035
13036        It would be nice to remove this kludge and instead gather all
13037        the information while building the game list.  (And to keep it
13038        in the game list nodes instead of having a bunch of fixed-size
13039        parallel arrays.)  Note this will require getting each game's
13040        termination from the PGN tags, as the game list builder does
13041        not process the game moves.  --mann
13042        */
13043     cmailMsgLoaded = TRUE;
13044     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13045
13046     /* Load first game in the file or popup game menu */
13047     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13048
13049 #endif /* !WIN32 */
13050     return;
13051 }
13052
13053 int
13054 RegisterMove ()
13055 {
13056     FILE *f;
13057     char string[MSG_SIZ];
13058
13059     if (   cmailMailedMove
13060         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13061         return TRUE;            /* Allow free viewing  */
13062     }
13063
13064     /* Unregister move to ensure that we don't leave RegisterMove        */
13065     /* with the move registered when the conditions for registering no   */
13066     /* longer hold                                                       */
13067     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13068         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13069         nCmailMovesRegistered --;
13070
13071         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13072           {
13073               free(cmailCommentList[lastLoadGameNumber - 1]);
13074               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13075           }
13076     }
13077
13078     if (cmailOldMove == -1) {
13079         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13080         return FALSE;
13081     }
13082
13083     if (currentMove > cmailOldMove + 1) {
13084         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13085         return FALSE;
13086     }
13087
13088     if (currentMove < cmailOldMove) {
13089         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13090         return FALSE;
13091     }
13092
13093     if (forwardMostMove > currentMove) {
13094         /* Silently truncate extra moves */
13095         TruncateGame();
13096     }
13097
13098     if (   (currentMove == cmailOldMove + 1)
13099         || (   (currentMove == cmailOldMove)
13100             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13101                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13102         if (gameInfo.result != GameUnfinished) {
13103             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13104         }
13105
13106         if (commentList[currentMove] != NULL) {
13107             cmailCommentList[lastLoadGameNumber - 1]
13108               = StrSave(commentList[currentMove]);
13109         }
13110         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13111
13112         if (appData.debugMode)
13113           fprintf(debugFP, "Saving %s for game %d\n",
13114                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13115
13116         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13117
13118         f = fopen(string, "w");
13119         if (appData.oldSaveStyle) {
13120             SaveGameOldStyle(f); /* also closes the file */
13121
13122             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13123             f = fopen(string, "w");
13124             SavePosition(f, 0, NULL); /* also closes the file */
13125         } else {
13126             fprintf(f, "{--------------\n");
13127             PrintPosition(f, currentMove);
13128             fprintf(f, "--------------}\n\n");
13129
13130             SaveGame(f, 0, NULL); /* also closes the file*/
13131         }
13132
13133         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13134         nCmailMovesRegistered ++;
13135     } else if (nCmailGames == 1) {
13136         DisplayError(_("You have not made a move yet"), 0);
13137         return FALSE;
13138     }
13139
13140     return TRUE;
13141 }
13142
13143 void
13144 MailMoveEvent ()
13145 {
13146 #if !WIN32
13147     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13148     FILE *commandOutput;
13149     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13150     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13151     int nBuffers;
13152     int i;
13153     int archived;
13154     char *arcDir;
13155
13156     if (! cmailMsgLoaded) {
13157         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13158         return;
13159     }
13160
13161     if (nCmailGames == nCmailResults) {
13162         DisplayError(_("No unfinished games"), 0);
13163         return;
13164     }
13165
13166 #if CMAIL_PROHIBIT_REMAIL
13167     if (cmailMailedMove) {
13168       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);
13169         DisplayError(msg, 0);
13170         return;
13171     }
13172 #endif
13173
13174     if (! (cmailMailedMove || RegisterMove())) return;
13175
13176     if (   cmailMailedMove
13177         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13178       snprintf(string, MSG_SIZ, partCommandString,
13179                appData.debugMode ? " -v" : "", appData.cmailGameName);
13180         commandOutput = popen(string, "r");
13181
13182         if (commandOutput == NULL) {
13183             DisplayError(_("Failed to invoke cmail"), 0);
13184         } else {
13185             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13186                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13187             }
13188             if (nBuffers > 1) {
13189                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13190                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13191                 nBytes = MSG_SIZ - 1;
13192             } else {
13193                 (void) memcpy(msg, buffer, nBytes);
13194             }
13195             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13196
13197             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13198                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13199
13200                 archived = TRUE;
13201                 for (i = 0; i < nCmailGames; i ++) {
13202                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13203                         archived = FALSE;
13204                     }
13205                 }
13206                 if (   archived
13207                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13208                         != NULL)) {
13209                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13210                            arcDir,
13211                            appData.cmailGameName,
13212                            gameInfo.date);
13213                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13214                     cmailMsgLoaded = FALSE;
13215                 }
13216             }
13217
13218             DisplayInformation(msg);
13219             pclose(commandOutput);
13220         }
13221     } else {
13222         if ((*cmailMsg) != '\0') {
13223             DisplayInformation(cmailMsg);
13224         }
13225     }
13226
13227     return;
13228 #endif /* !WIN32 */
13229 }
13230
13231 char *
13232 CmailMsg ()
13233 {
13234 #if WIN32
13235     return NULL;
13236 #else
13237     int  prependComma = 0;
13238     char number[5];
13239     char string[MSG_SIZ];       /* Space for game-list */
13240     int  i;
13241
13242     if (!cmailMsgLoaded) return "";
13243
13244     if (cmailMailedMove) {
13245       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13246     } else {
13247         /* Create a list of games left */
13248       snprintf(string, MSG_SIZ, "[");
13249         for (i = 0; i < nCmailGames; i ++) {
13250             if (! (   cmailMoveRegistered[i]
13251                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13252                 if (prependComma) {
13253                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13254                 } else {
13255                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13256                     prependComma = 1;
13257                 }
13258
13259                 strcat(string, number);
13260             }
13261         }
13262         strcat(string, "]");
13263
13264         if (nCmailMovesRegistered + nCmailResults == 0) {
13265             switch (nCmailGames) {
13266               case 1:
13267                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13268                 break;
13269
13270               case 2:
13271                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13272                 break;
13273
13274               default:
13275                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13276                          nCmailGames);
13277                 break;
13278             }
13279         } else {
13280             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13281               case 1:
13282                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13283                          string);
13284                 break;
13285
13286               case 0:
13287                 if (nCmailResults == nCmailGames) {
13288                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13289                 } else {
13290                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13291                 }
13292                 break;
13293
13294               default:
13295                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13296                          string);
13297             }
13298         }
13299     }
13300     return cmailMsg;
13301 #endif /* WIN32 */
13302 }
13303
13304 void
13305 ResetGameEvent ()
13306 {
13307     if (gameMode == Training)
13308       SetTrainingModeOff();
13309
13310     Reset(TRUE, TRUE);
13311     cmailMsgLoaded = FALSE;
13312     if (appData.icsActive) {
13313       SendToICS(ics_prefix);
13314       SendToICS("refresh\n");
13315     }
13316 }
13317
13318 void
13319 ExitEvent (int status)
13320 {
13321     exiting++;
13322     if (exiting > 2) {
13323       /* Give up on clean exit */
13324       exit(status);
13325     }
13326     if (exiting > 1) {
13327       /* Keep trying for clean exit */
13328       return;
13329     }
13330
13331     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13332
13333     if (telnetISR != NULL) {
13334       RemoveInputSource(telnetISR);
13335     }
13336     if (icsPR != NoProc) {
13337       DestroyChildProcess(icsPR, TRUE);
13338     }
13339
13340     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13341     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13342
13343     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13344     /* make sure this other one finishes before killing it!                  */
13345     if(endingGame) { int count = 0;
13346         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13347         while(endingGame && count++ < 10) DoSleep(1);
13348         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13349     }
13350
13351     /* Kill off chess programs */
13352     if (first.pr != NoProc) {
13353         ExitAnalyzeMode();
13354
13355         DoSleep( appData.delayBeforeQuit );
13356         SendToProgram("quit\n", &first);
13357         DoSleep( appData.delayAfterQuit );
13358         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13359     }
13360     if (second.pr != NoProc) {
13361         DoSleep( appData.delayBeforeQuit );
13362         SendToProgram("quit\n", &second);
13363         DoSleep( appData.delayAfterQuit );
13364         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13365     }
13366     if (first.isr != NULL) {
13367         RemoveInputSource(first.isr);
13368     }
13369     if (second.isr != NULL) {
13370         RemoveInputSource(second.isr);
13371     }
13372
13373     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13374     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13375
13376     ShutDownFrontEnd();
13377     exit(status);
13378 }
13379
13380 void
13381 PauseEngine (ChessProgramState *cps)
13382 {
13383     SendToProgram("pause\n", cps);
13384     cps->pause = 2;
13385 }
13386
13387 void
13388 UnPauseEngine (ChessProgramState *cps)
13389 {
13390     SendToProgram("resume\n", cps);
13391     cps->pause = 1;
13392 }
13393
13394 void
13395 PauseEvent ()
13396 {
13397     if (appData.debugMode)
13398         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13399     if (pausing) {
13400         pausing = FALSE;
13401         ModeHighlight();
13402         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13403             StartClocks();
13404             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13405                 if(stalledEngine->other->pause) UnPauseEngine(stalledEngine->other);
13406                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13407             }
13408             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13409             HandleMachineMove(stashedInputMove, stalledEngine);
13410             stalledEngine = NULL;
13411             return;
13412         }
13413         if (gameMode == MachinePlaysWhite ||
13414             gameMode == TwoMachinesPlay   ||
13415             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13416             if(first.pause)  UnPauseEngine(&first);
13417             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13418             if(second.pause) UnPauseEngine(&second);
13419             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13420             StartClocks();
13421         } else {
13422             DisplayBothClocks();
13423         }
13424         if (gameMode == PlayFromGameFile) {
13425             if (appData.timeDelay >= 0)
13426                 AutoPlayGameLoop();
13427         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13428             Reset(FALSE, TRUE);
13429             SendToICS(ics_prefix);
13430             SendToICS("refresh\n");
13431         } else if (currentMove < forwardMostMove) {
13432             ForwardInner(forwardMostMove);
13433         }
13434         pauseExamInvalid = FALSE;
13435     } else {
13436         switch (gameMode) {
13437           default:
13438             return;
13439           case IcsExamining:
13440             pauseExamForwardMostMove = forwardMostMove;
13441             pauseExamInvalid = FALSE;
13442             /* fall through */
13443           case IcsObserving:
13444           case IcsPlayingWhite:
13445           case IcsPlayingBlack:
13446             pausing = TRUE;
13447             ModeHighlight();
13448             return;
13449           case PlayFromGameFile:
13450             (void) StopLoadGameTimer();
13451             pausing = TRUE;
13452             ModeHighlight();
13453             break;
13454           case BeginningOfGame:
13455             if (appData.icsActive) return;
13456             /* else fall through */
13457           case MachinePlaysWhite:
13458           case MachinePlaysBlack:
13459           case TwoMachinesPlay:
13460             if (forwardMostMove == 0)
13461               return;           /* don't pause if no one has moved */
13462             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13463                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13464                 if(onMove->pause) {           // thinking engine can be paused
13465                     PauseEngine(onMove);      // do it
13466                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13467                         PauseEngine(onMove->other);
13468                     else
13469                         SendToProgram("easy\n", onMove->other);
13470                     StopClocks();
13471                 }
13472             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13473                 if(first.pause) {
13474                     PauseEngine(&first);
13475                     StopClocks();
13476                 }
13477             } else { // human on move, pause pondering by either method
13478                 if(first.pause) 
13479                     PauseEngine(&first);
13480                 else
13481                     SendToProgram("easy\n", &first);
13482                 StopClocks();
13483             }
13484             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13485           case AnalyzeMode:
13486             pausing = TRUE;
13487             ModeHighlight();
13488             break;
13489         }
13490     }
13491 }
13492
13493 void
13494 EditCommentEvent ()
13495 {
13496     char title[MSG_SIZ];
13497
13498     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13499       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13500     } else {
13501       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13502                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13503                parseList[currentMove - 1]);
13504     }
13505
13506     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13507 }
13508
13509
13510 void
13511 EditTagsEvent ()
13512 {
13513     char *tags = PGNTags(&gameInfo);
13514     bookUp = FALSE;
13515     EditTagsPopUp(tags, NULL);
13516     free(tags);
13517 }
13518
13519 void
13520 ToggleSecond ()
13521 {
13522   if(second.analyzing) {
13523     SendToProgram("exit\n", &second);
13524     second.analyzing = FALSE;
13525   } else {
13526     if (second.pr == NoProc) StartChessProgram(&second);
13527     InitChessProgram(&second, FALSE);
13528     FeedMovesToProgram(&second, currentMove);
13529
13530     SendToProgram("analyze\n", &second);
13531     second.analyzing = TRUE;
13532   }
13533 }
13534
13535 /* Toggle ShowThinking */
13536 void
13537 ToggleShowThinking()
13538 {
13539   appData.showThinking = !appData.showThinking;
13540   ShowThinkingEvent();
13541 }
13542
13543 int
13544 AnalyzeModeEvent ()
13545 {
13546     char buf[MSG_SIZ];
13547
13548     if (!first.analysisSupport) {
13549       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13550       DisplayError(buf, 0);
13551       return 0;
13552     }
13553     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13554     if (appData.icsActive) {
13555         if (gameMode != IcsObserving) {
13556           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13557             DisplayError(buf, 0);
13558             /* secure check */
13559             if (appData.icsEngineAnalyze) {
13560                 if (appData.debugMode)
13561                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13562                 ExitAnalyzeMode();
13563                 ModeHighlight();
13564             }
13565             return 0;
13566         }
13567         /* if enable, user wants to disable icsEngineAnalyze */
13568         if (appData.icsEngineAnalyze) {
13569                 ExitAnalyzeMode();
13570                 ModeHighlight();
13571                 return 0;
13572         }
13573         appData.icsEngineAnalyze = TRUE;
13574         if (appData.debugMode)
13575             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13576     }
13577
13578     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13579     if (appData.noChessProgram || gameMode == AnalyzeMode)
13580       return 0;
13581
13582     if (gameMode != AnalyzeFile) {
13583         if (!appData.icsEngineAnalyze) {
13584                EditGameEvent();
13585                if (gameMode != EditGame) return 0;
13586         }
13587         if (!appData.showThinking) ToggleShowThinking();
13588         ResurrectChessProgram();
13589         SendToProgram("analyze\n", &first);
13590         first.analyzing = TRUE;
13591         /*first.maybeThinking = TRUE;*/
13592         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13593         EngineOutputPopUp();
13594     }
13595     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13596     pausing = FALSE;
13597     ModeHighlight();
13598     SetGameInfo();
13599
13600     StartAnalysisClock();
13601     GetTimeMark(&lastNodeCountTime);
13602     lastNodeCount = 0;
13603     return 1;
13604 }
13605
13606 void
13607 AnalyzeFileEvent ()
13608 {
13609     if (appData.noChessProgram || gameMode == AnalyzeFile)
13610       return;
13611
13612     if (!first.analysisSupport) {
13613       char buf[MSG_SIZ];
13614       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13615       DisplayError(buf, 0);
13616       return;
13617     }
13618
13619     if (gameMode != AnalyzeMode) {
13620         EditGameEvent();
13621         if (gameMode != EditGame) return;
13622         if (!appData.showThinking) ToggleShowThinking();
13623         ResurrectChessProgram();
13624         SendToProgram("analyze\n", &first);
13625         first.analyzing = TRUE;
13626         /*first.maybeThinking = TRUE;*/
13627         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13628         EngineOutputPopUp();
13629     }
13630     gameMode = AnalyzeFile;
13631     pausing = FALSE;
13632     ModeHighlight();
13633     SetGameInfo();
13634
13635     StartAnalysisClock();
13636     GetTimeMark(&lastNodeCountTime);
13637     lastNodeCount = 0;
13638     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13639     AnalysisPeriodicEvent(1);
13640 }
13641
13642 void
13643 MachineWhiteEvent ()
13644 {
13645     char buf[MSG_SIZ];
13646     char *bookHit = NULL;
13647
13648     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13649       return;
13650
13651
13652     if (gameMode == PlayFromGameFile ||
13653         gameMode == TwoMachinesPlay  ||
13654         gameMode == Training         ||
13655         gameMode == AnalyzeMode      ||
13656         gameMode == EndOfGame)
13657         EditGameEvent();
13658
13659     if (gameMode == EditPosition)
13660         EditPositionDone(TRUE);
13661
13662     if (!WhiteOnMove(currentMove)) {
13663         DisplayError(_("It is not White's turn"), 0);
13664         return;
13665     }
13666
13667     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13668       ExitAnalyzeMode();
13669
13670     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13671         gameMode == AnalyzeFile)
13672         TruncateGame();
13673
13674     ResurrectChessProgram();    /* in case it isn't running */
13675     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13676         gameMode = MachinePlaysWhite;
13677         ResetClocks();
13678     } else
13679     gameMode = MachinePlaysWhite;
13680     pausing = FALSE;
13681     ModeHighlight();
13682     SetGameInfo();
13683     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13684     DisplayTitle(buf);
13685     if (first.sendName) {
13686       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13687       SendToProgram(buf, &first);
13688     }
13689     if (first.sendTime) {
13690       if (first.useColors) {
13691         SendToProgram("black\n", &first); /*gnu kludge*/
13692       }
13693       SendTimeRemaining(&first, TRUE);
13694     }
13695     if (first.useColors) {
13696       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13697     }
13698     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13699     SetMachineThinkingEnables();
13700     first.maybeThinking = TRUE;
13701     StartClocks();
13702     firstMove = FALSE;
13703
13704     if (appData.autoFlipView && !flipView) {
13705       flipView = !flipView;
13706       DrawPosition(FALSE, NULL);
13707       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13708     }
13709
13710     if(bookHit) { // [HGM] book: simulate book reply
13711         static char bookMove[MSG_SIZ]; // a bit generous?
13712
13713         programStats.nodes = programStats.depth = programStats.time =
13714         programStats.score = programStats.got_only_move = 0;
13715         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13716
13717         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13718         strcat(bookMove, bookHit);
13719         HandleMachineMove(bookMove, &first);
13720     }
13721 }
13722
13723 void
13724 MachineBlackEvent ()
13725 {
13726   char buf[MSG_SIZ];
13727   char *bookHit = NULL;
13728
13729     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13730         return;
13731
13732
13733     if (gameMode == PlayFromGameFile ||
13734         gameMode == TwoMachinesPlay  ||
13735         gameMode == Training         ||
13736         gameMode == AnalyzeMode      ||
13737         gameMode == EndOfGame)
13738         EditGameEvent();
13739
13740     if (gameMode == EditPosition)
13741         EditPositionDone(TRUE);
13742
13743     if (WhiteOnMove(currentMove)) {
13744         DisplayError(_("It is not Black's turn"), 0);
13745         return;
13746     }
13747
13748     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13749       ExitAnalyzeMode();
13750
13751     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13752         gameMode == AnalyzeFile)
13753         TruncateGame();
13754
13755     ResurrectChessProgram();    /* in case it isn't running */
13756     gameMode = MachinePlaysBlack;
13757     pausing = FALSE;
13758     ModeHighlight();
13759     SetGameInfo();
13760     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13761     DisplayTitle(buf);
13762     if (first.sendName) {
13763       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13764       SendToProgram(buf, &first);
13765     }
13766     if (first.sendTime) {
13767       if (first.useColors) {
13768         SendToProgram("white\n", &first); /*gnu kludge*/
13769       }
13770       SendTimeRemaining(&first, FALSE);
13771     }
13772     if (first.useColors) {
13773       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13774     }
13775     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13776     SetMachineThinkingEnables();
13777     first.maybeThinking = TRUE;
13778     StartClocks();
13779
13780     if (appData.autoFlipView && flipView) {
13781       flipView = !flipView;
13782       DrawPosition(FALSE, NULL);
13783       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13784     }
13785     if(bookHit) { // [HGM] book: simulate book reply
13786         static char bookMove[MSG_SIZ]; // a bit generous?
13787
13788         programStats.nodes = programStats.depth = programStats.time =
13789         programStats.score = programStats.got_only_move = 0;
13790         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13791
13792         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13793         strcat(bookMove, bookHit);
13794         HandleMachineMove(bookMove, &first);
13795     }
13796 }
13797
13798
13799 void
13800 DisplayTwoMachinesTitle ()
13801 {
13802     char buf[MSG_SIZ];
13803     if (appData.matchGames > 0) {
13804         if(appData.tourneyFile[0]) {
13805           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13806                    gameInfo.white, _("vs."), gameInfo.black,
13807                    nextGame+1, appData.matchGames+1,
13808                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13809         } else 
13810         if (first.twoMachinesColor[0] == 'w') {
13811           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13812                    gameInfo.white, _("vs."),  gameInfo.black,
13813                    first.matchWins, second.matchWins,
13814                    matchGame - 1 - (first.matchWins + second.matchWins));
13815         } else {
13816           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13817                    gameInfo.white, _("vs."), gameInfo.black,
13818                    second.matchWins, first.matchWins,
13819                    matchGame - 1 - (first.matchWins + second.matchWins));
13820         }
13821     } else {
13822       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13823     }
13824     DisplayTitle(buf);
13825 }
13826
13827 void
13828 SettingsMenuIfReady ()
13829 {
13830   if (second.lastPing != second.lastPong) {
13831     DisplayMessage("", _("Waiting for second chess program"));
13832     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13833     return;
13834   }
13835   ThawUI();
13836   DisplayMessage("", "");
13837   SettingsPopUp(&second);
13838 }
13839
13840 int
13841 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13842 {
13843     char buf[MSG_SIZ];
13844     if (cps->pr == NoProc) {
13845         StartChessProgram(cps);
13846         if (cps->protocolVersion == 1) {
13847           retry();
13848         } else {
13849           /* kludge: allow timeout for initial "feature" command */
13850           FreezeUI();
13851           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13852           DisplayMessage("", buf);
13853           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13854         }
13855         return 1;
13856     }
13857     return 0;
13858 }
13859
13860 void
13861 TwoMachinesEvent P((void))
13862 {
13863     int i;
13864     char buf[MSG_SIZ];
13865     ChessProgramState *onmove;
13866     char *bookHit = NULL;
13867     static int stalling = 0;
13868     TimeMark now;
13869     long wait;
13870
13871     if (appData.noChessProgram) return;
13872
13873     switch (gameMode) {
13874       case TwoMachinesPlay:
13875         return;
13876       case MachinePlaysWhite:
13877       case MachinePlaysBlack:
13878         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13879             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13880             return;
13881         }
13882         /* fall through */
13883       case BeginningOfGame:
13884       case PlayFromGameFile:
13885       case EndOfGame:
13886         EditGameEvent();
13887         if (gameMode != EditGame) return;
13888         break;
13889       case EditPosition:
13890         EditPositionDone(TRUE);
13891         break;
13892       case AnalyzeMode:
13893       case AnalyzeFile:
13894         ExitAnalyzeMode();
13895         break;
13896       case EditGame:
13897       default:
13898         break;
13899     }
13900
13901 //    forwardMostMove = currentMove;
13902     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13903
13904     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13905
13906     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13907     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13908       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13909       return;
13910     }
13911
13912     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13913         DisplayError("second engine does not play this", 0);
13914         return;
13915     }
13916
13917     if(!stalling) {
13918       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13919       SendToProgram("force\n", &second);
13920       stalling = 1;
13921       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13922       return;
13923     }
13924     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13925     if(appData.matchPause>10000 || appData.matchPause<10)
13926                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13927     wait = SubtractTimeMarks(&now, &pauseStart);
13928     if(wait < appData.matchPause) {
13929         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13930         return;
13931     }
13932     // we are now committed to starting the game
13933     stalling = 0;
13934     DisplayMessage("", "");
13935     if (startedFromSetupPosition) {
13936         SendBoard(&second, backwardMostMove);
13937     if (appData.debugMode) {
13938         fprintf(debugFP, "Two Machines\n");
13939     }
13940     }
13941     for (i = backwardMostMove; i < forwardMostMove; i++) {
13942         SendMoveToProgram(i, &second);
13943     }
13944
13945     gameMode = TwoMachinesPlay;
13946     pausing = FALSE;
13947     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13948     SetGameInfo();
13949     DisplayTwoMachinesTitle();
13950     firstMove = TRUE;
13951     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13952         onmove = &first;
13953     } else {
13954         onmove = &second;
13955     }
13956     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13957     SendToProgram(first.computerString, &first);
13958     if (first.sendName) {
13959       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13960       SendToProgram(buf, &first);
13961     }
13962     SendToProgram(second.computerString, &second);
13963     if (second.sendName) {
13964       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13965       SendToProgram(buf, &second);
13966     }
13967
13968     ResetClocks();
13969     if (!first.sendTime || !second.sendTime) {
13970         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13971         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13972     }
13973     if (onmove->sendTime) {
13974       if (onmove->useColors) {
13975         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13976       }
13977       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13978     }
13979     if (onmove->useColors) {
13980       SendToProgram(onmove->twoMachinesColor, onmove);
13981     }
13982     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13983 //    SendToProgram("go\n", onmove);
13984     onmove->maybeThinking = TRUE;
13985     SetMachineThinkingEnables();
13986
13987     StartClocks();
13988
13989     if(bookHit) { // [HGM] book: simulate book reply
13990         static char bookMove[MSG_SIZ]; // a bit generous?
13991
13992         programStats.nodes = programStats.depth = programStats.time =
13993         programStats.score = programStats.got_only_move = 0;
13994         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13995
13996         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13997         strcat(bookMove, bookHit);
13998         savedMessage = bookMove; // args for deferred call
13999         savedState = onmove;
14000         ScheduleDelayedEvent(DeferredBookMove, 1);
14001     }
14002 }
14003
14004 void
14005 TrainingEvent ()
14006 {
14007     if (gameMode == Training) {
14008       SetTrainingModeOff();
14009       gameMode = PlayFromGameFile;
14010       DisplayMessage("", _("Training mode off"));
14011     } else {
14012       gameMode = Training;
14013       animateTraining = appData.animate;
14014
14015       /* make sure we are not already at the end of the game */
14016       if (currentMove < forwardMostMove) {
14017         SetTrainingModeOn();
14018         DisplayMessage("", _("Training mode on"));
14019       } else {
14020         gameMode = PlayFromGameFile;
14021         DisplayError(_("Already at end of game"), 0);
14022       }
14023     }
14024     ModeHighlight();
14025 }
14026
14027 void
14028 IcsClientEvent ()
14029 {
14030     if (!appData.icsActive) return;
14031     switch (gameMode) {
14032       case IcsPlayingWhite:
14033       case IcsPlayingBlack:
14034       case IcsObserving:
14035       case IcsIdle:
14036       case BeginningOfGame:
14037       case IcsExamining:
14038         return;
14039
14040       case EditGame:
14041         break;
14042
14043       case EditPosition:
14044         EditPositionDone(TRUE);
14045         break;
14046
14047       case AnalyzeMode:
14048       case AnalyzeFile:
14049         ExitAnalyzeMode();
14050         break;
14051
14052       default:
14053         EditGameEvent();
14054         break;
14055     }
14056
14057     gameMode = IcsIdle;
14058     ModeHighlight();
14059     return;
14060 }
14061
14062 void
14063 EditGameEvent ()
14064 {
14065     int i;
14066
14067     switch (gameMode) {
14068       case Training:
14069         SetTrainingModeOff();
14070         break;
14071       case MachinePlaysWhite:
14072       case MachinePlaysBlack:
14073       case BeginningOfGame:
14074         SendToProgram("force\n", &first);
14075         SetUserThinkingEnables();
14076         break;
14077       case PlayFromGameFile:
14078         (void) StopLoadGameTimer();
14079         if (gameFileFP != NULL) {
14080             gameFileFP = NULL;
14081         }
14082         break;
14083       case EditPosition:
14084         EditPositionDone(TRUE);
14085         break;
14086       case AnalyzeMode:
14087       case AnalyzeFile:
14088         ExitAnalyzeMode();
14089         SendToProgram("force\n", &first);
14090         break;
14091       case TwoMachinesPlay:
14092         GameEnds(EndOfFile, NULL, GE_PLAYER);
14093         ResurrectChessProgram();
14094         SetUserThinkingEnables();
14095         break;
14096       case EndOfGame:
14097         ResurrectChessProgram();
14098         break;
14099       case IcsPlayingBlack:
14100       case IcsPlayingWhite:
14101         DisplayError(_("Warning: You are still playing a game"), 0);
14102         break;
14103       case IcsObserving:
14104         DisplayError(_("Warning: You are still observing a game"), 0);
14105         break;
14106       case IcsExamining:
14107         DisplayError(_("Warning: You are still examining a game"), 0);
14108         break;
14109       case IcsIdle:
14110         break;
14111       case EditGame:
14112       default:
14113         return;
14114     }
14115
14116     pausing = FALSE;
14117     StopClocks();
14118     first.offeredDraw = second.offeredDraw = 0;
14119
14120     if (gameMode == PlayFromGameFile) {
14121         whiteTimeRemaining = timeRemaining[0][currentMove];
14122         blackTimeRemaining = timeRemaining[1][currentMove];
14123         DisplayTitle("");
14124     }
14125
14126     if (gameMode == MachinePlaysWhite ||
14127         gameMode == MachinePlaysBlack ||
14128         gameMode == TwoMachinesPlay ||
14129         gameMode == EndOfGame) {
14130         i = forwardMostMove;
14131         while (i > currentMove) {
14132             SendToProgram("undo\n", &first);
14133             i--;
14134         }
14135         if(!adjustedClock) {
14136         whiteTimeRemaining = timeRemaining[0][currentMove];
14137         blackTimeRemaining = timeRemaining[1][currentMove];
14138         DisplayBothClocks();
14139         }
14140         if (whiteFlag || blackFlag) {
14141             whiteFlag = blackFlag = 0;
14142         }
14143         DisplayTitle("");
14144     }
14145
14146     gameMode = EditGame;
14147     ModeHighlight();
14148     SetGameInfo();
14149 }
14150
14151
14152 void
14153 EditPositionEvent ()
14154 {
14155     if (gameMode == EditPosition) {
14156         EditGameEvent();
14157         return;
14158     }
14159
14160     EditGameEvent();
14161     if (gameMode != EditGame) return;
14162
14163     gameMode = EditPosition;
14164     ModeHighlight();
14165     SetGameInfo();
14166     if (currentMove > 0)
14167       CopyBoard(boards[0], boards[currentMove]);
14168
14169     blackPlaysFirst = !WhiteOnMove(currentMove);
14170     ResetClocks();
14171     currentMove = forwardMostMove = backwardMostMove = 0;
14172     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14173     DisplayMove(-1);
14174     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14175 }
14176
14177 void
14178 ExitAnalyzeMode ()
14179 {
14180     /* [DM] icsEngineAnalyze - possible call from other functions */
14181     if (appData.icsEngineAnalyze) {
14182         appData.icsEngineAnalyze = FALSE;
14183
14184         DisplayMessage("",_("Close ICS engine analyze..."));
14185     }
14186     if (first.analysisSupport && first.analyzing) {
14187       SendToBoth("exit\n");
14188       first.analyzing = second.analyzing = FALSE;
14189     }
14190     thinkOutput[0] = NULLCHAR;
14191 }
14192
14193 void
14194 EditPositionDone (Boolean fakeRights)
14195 {
14196     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14197
14198     startedFromSetupPosition = TRUE;
14199     InitChessProgram(&first, FALSE);
14200     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14201       boards[0][EP_STATUS] = EP_NONE;
14202       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14203       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14204         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14205         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14206       } else boards[0][CASTLING][2] = NoRights;
14207       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14208         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14209         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14210       } else boards[0][CASTLING][5] = NoRights;
14211       if(gameInfo.variant == VariantSChess) {
14212         int i;
14213         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14214           boards[0][VIRGIN][i] = 0;
14215           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14216           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14217         }
14218       }
14219     }
14220     SendToProgram("force\n", &first);
14221     if (blackPlaysFirst) {
14222         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14223         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14224         currentMove = forwardMostMove = backwardMostMove = 1;
14225         CopyBoard(boards[1], boards[0]);
14226     } else {
14227         currentMove = forwardMostMove = backwardMostMove = 0;
14228     }
14229     SendBoard(&first, forwardMostMove);
14230     if (appData.debugMode) {
14231         fprintf(debugFP, "EditPosDone\n");
14232     }
14233     DisplayTitle("");
14234     DisplayMessage("", "");
14235     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14236     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14237     gameMode = EditGame;
14238     ModeHighlight();
14239     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14240     ClearHighlights(); /* [AS] */
14241 }
14242
14243 /* Pause for `ms' milliseconds */
14244 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14245 void
14246 TimeDelay (long ms)
14247 {
14248     TimeMark m1, m2;
14249
14250     GetTimeMark(&m1);
14251     do {
14252         GetTimeMark(&m2);
14253     } while (SubtractTimeMarks(&m2, &m1) < ms);
14254 }
14255
14256 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14257 void
14258 SendMultiLineToICS (char *buf)
14259 {
14260     char temp[MSG_SIZ+1], *p;
14261     int len;
14262
14263     len = strlen(buf);
14264     if (len > MSG_SIZ)
14265       len = MSG_SIZ;
14266
14267     strncpy(temp, buf, len);
14268     temp[len] = 0;
14269
14270     p = temp;
14271     while (*p) {
14272         if (*p == '\n' || *p == '\r')
14273           *p = ' ';
14274         ++p;
14275     }
14276
14277     strcat(temp, "\n");
14278     SendToICS(temp);
14279     SendToPlayer(temp, strlen(temp));
14280 }
14281
14282 void
14283 SetWhiteToPlayEvent ()
14284 {
14285     if (gameMode == EditPosition) {
14286         blackPlaysFirst = FALSE;
14287         DisplayBothClocks();    /* works because currentMove is 0 */
14288     } else if (gameMode == IcsExamining) {
14289         SendToICS(ics_prefix);
14290         SendToICS("tomove white\n");
14291     }
14292 }
14293
14294 void
14295 SetBlackToPlayEvent ()
14296 {
14297     if (gameMode == EditPosition) {
14298         blackPlaysFirst = TRUE;
14299         currentMove = 1;        /* kludge */
14300         DisplayBothClocks();
14301         currentMove = 0;
14302     } else if (gameMode == IcsExamining) {
14303         SendToICS(ics_prefix);
14304         SendToICS("tomove black\n");
14305     }
14306 }
14307
14308 void
14309 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14310 {
14311     char buf[MSG_SIZ];
14312     ChessSquare piece = boards[0][y][x];
14313
14314     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14315
14316     switch (selection) {
14317       case ClearBoard:
14318         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14319             SendToICS(ics_prefix);
14320             SendToICS("bsetup clear\n");
14321         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14322             SendToICS(ics_prefix);
14323             SendToICS("clearboard\n");
14324         } else {
14325             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14326                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14327                 for (y = 0; y < BOARD_HEIGHT; y++) {
14328                     if (gameMode == IcsExamining) {
14329                         if (boards[currentMove][y][x] != EmptySquare) {
14330                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14331                                     AAA + x, ONE + y);
14332                             SendToICS(buf);
14333                         }
14334                     } else {
14335                         boards[0][y][x] = p;
14336                     }
14337                 }
14338             }
14339         }
14340         if (gameMode == EditPosition) {
14341             DrawPosition(FALSE, boards[0]);
14342         }
14343         break;
14344
14345       case WhitePlay:
14346         SetWhiteToPlayEvent();
14347         break;
14348
14349       case BlackPlay:
14350         SetBlackToPlayEvent();
14351         break;
14352
14353       case EmptySquare:
14354         if (gameMode == IcsExamining) {
14355             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14356             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14357             SendToICS(buf);
14358         } else {
14359             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14360                 if(x == BOARD_LEFT-2) {
14361                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14362                     boards[0][y][1] = 0;
14363                 } else
14364                 if(x == BOARD_RGHT+1) {
14365                     if(y >= gameInfo.holdingsSize) break;
14366                     boards[0][y][BOARD_WIDTH-2] = 0;
14367                 } else break;
14368             }
14369             boards[0][y][x] = EmptySquare;
14370             DrawPosition(FALSE, boards[0]);
14371         }
14372         break;
14373
14374       case PromotePiece:
14375         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14376            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14377             selection = (ChessSquare) (PROMOTED piece);
14378         } else if(piece == EmptySquare) selection = WhiteSilver;
14379         else selection = (ChessSquare)((int)piece - 1);
14380         goto defaultlabel;
14381
14382       case DemotePiece:
14383         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14384            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14385             selection = (ChessSquare) (DEMOTED piece);
14386         } else if(piece == EmptySquare) selection = BlackSilver;
14387         else selection = (ChessSquare)((int)piece + 1);
14388         goto defaultlabel;
14389
14390       case WhiteQueen:
14391       case BlackQueen:
14392         if(gameInfo.variant == VariantShatranj ||
14393            gameInfo.variant == VariantXiangqi  ||
14394            gameInfo.variant == VariantCourier  ||
14395            gameInfo.variant == VariantMakruk     )
14396             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14397         goto defaultlabel;
14398
14399       case WhiteKing:
14400       case BlackKing:
14401         if(gameInfo.variant == VariantXiangqi)
14402             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14403         if(gameInfo.variant == VariantKnightmate)
14404             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14405       default:
14406         defaultlabel:
14407         if (gameMode == IcsExamining) {
14408             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14409             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14410                      PieceToChar(selection), AAA + x, ONE + y);
14411             SendToICS(buf);
14412         } else {
14413             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14414                 int n;
14415                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14416                     n = PieceToNumber(selection - BlackPawn);
14417                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14418                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14419                     boards[0][BOARD_HEIGHT-1-n][1]++;
14420                 } else
14421                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14422                     n = PieceToNumber(selection);
14423                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14424                     boards[0][n][BOARD_WIDTH-1] = selection;
14425                     boards[0][n][BOARD_WIDTH-2]++;
14426                 }
14427             } else
14428             boards[0][y][x] = selection;
14429             DrawPosition(TRUE, boards[0]);
14430             ClearHighlights();
14431             fromX = fromY = -1;
14432         }
14433         break;
14434     }
14435 }
14436
14437
14438 void
14439 DropMenuEvent (ChessSquare selection, int x, int y)
14440 {
14441     ChessMove moveType;
14442
14443     switch (gameMode) {
14444       case IcsPlayingWhite:
14445       case MachinePlaysBlack:
14446         if (!WhiteOnMove(currentMove)) {
14447             DisplayMoveError(_("It is Black's turn"));
14448             return;
14449         }
14450         moveType = WhiteDrop;
14451         break;
14452       case IcsPlayingBlack:
14453       case MachinePlaysWhite:
14454         if (WhiteOnMove(currentMove)) {
14455             DisplayMoveError(_("It is White's turn"));
14456             return;
14457         }
14458         moveType = BlackDrop;
14459         break;
14460       case EditGame:
14461         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14462         break;
14463       default:
14464         return;
14465     }
14466
14467     if (moveType == BlackDrop && selection < BlackPawn) {
14468       selection = (ChessSquare) ((int) selection
14469                                  + (int) BlackPawn - (int) WhitePawn);
14470     }
14471     if (boards[currentMove][y][x] != EmptySquare) {
14472         DisplayMoveError(_("That square is occupied"));
14473         return;
14474     }
14475
14476     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14477 }
14478
14479 void
14480 AcceptEvent ()
14481 {
14482     /* Accept a pending offer of any kind from opponent */
14483
14484     if (appData.icsActive) {
14485         SendToICS(ics_prefix);
14486         SendToICS("accept\n");
14487     } else if (cmailMsgLoaded) {
14488         if (currentMove == cmailOldMove &&
14489             commentList[cmailOldMove] != NULL &&
14490             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14491                    "Black offers a draw" : "White offers a draw")) {
14492             TruncateGame();
14493             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14494             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14495         } else {
14496             DisplayError(_("There is no pending offer on this move"), 0);
14497             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14498         }
14499     } else {
14500         /* Not used for offers from chess program */
14501     }
14502 }
14503
14504 void
14505 DeclineEvent ()
14506 {
14507     /* Decline a pending offer of any kind from opponent */
14508
14509     if (appData.icsActive) {
14510         SendToICS(ics_prefix);
14511         SendToICS("decline\n");
14512     } else if (cmailMsgLoaded) {
14513         if (currentMove == cmailOldMove &&
14514             commentList[cmailOldMove] != NULL &&
14515             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14516                    "Black offers a draw" : "White offers a draw")) {
14517 #ifdef NOTDEF
14518             AppendComment(cmailOldMove, "Draw declined", TRUE);
14519             DisplayComment(cmailOldMove - 1, "Draw declined");
14520 #endif /*NOTDEF*/
14521         } else {
14522             DisplayError(_("There is no pending offer on this move"), 0);
14523         }
14524     } else {
14525         /* Not used for offers from chess program */
14526     }
14527 }
14528
14529 void
14530 RematchEvent ()
14531 {
14532     /* Issue ICS rematch command */
14533     if (appData.icsActive) {
14534         SendToICS(ics_prefix);
14535         SendToICS("rematch\n");
14536     }
14537 }
14538
14539 void
14540 CallFlagEvent ()
14541 {
14542     /* Call your opponent's flag (claim a win on time) */
14543     if (appData.icsActive) {
14544         SendToICS(ics_prefix);
14545         SendToICS("flag\n");
14546     } else {
14547         switch (gameMode) {
14548           default:
14549             return;
14550           case MachinePlaysWhite:
14551             if (whiteFlag) {
14552                 if (blackFlag)
14553                   GameEnds(GameIsDrawn, "Both players ran out of time",
14554                            GE_PLAYER);
14555                 else
14556                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14557             } else {
14558                 DisplayError(_("Your opponent is not out of time"), 0);
14559             }
14560             break;
14561           case MachinePlaysBlack:
14562             if (blackFlag) {
14563                 if (whiteFlag)
14564                   GameEnds(GameIsDrawn, "Both players ran out of time",
14565                            GE_PLAYER);
14566                 else
14567                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14568             } else {
14569                 DisplayError(_("Your opponent is not out of time"), 0);
14570             }
14571             break;
14572         }
14573     }
14574 }
14575
14576 void
14577 ClockClick (int which)
14578 {       // [HGM] code moved to back-end from winboard.c
14579         if(which) { // black clock
14580           if (gameMode == EditPosition || gameMode == IcsExamining) {
14581             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14582             SetBlackToPlayEvent();
14583           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14584           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14585           } else if (shiftKey) {
14586             AdjustClock(which, -1);
14587           } else if (gameMode == IcsPlayingWhite ||
14588                      gameMode == MachinePlaysBlack) {
14589             CallFlagEvent();
14590           }
14591         } else { // white clock
14592           if (gameMode == EditPosition || gameMode == IcsExamining) {
14593             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14594             SetWhiteToPlayEvent();
14595           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14596           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14597           } else if (shiftKey) {
14598             AdjustClock(which, -1);
14599           } else if (gameMode == IcsPlayingBlack ||
14600                    gameMode == MachinePlaysWhite) {
14601             CallFlagEvent();
14602           }
14603         }
14604 }
14605
14606 void
14607 DrawEvent ()
14608 {
14609     /* Offer draw or accept pending draw offer from opponent */
14610
14611     if (appData.icsActive) {
14612         /* Note: tournament rules require draw offers to be
14613            made after you make your move but before you punch
14614            your clock.  Currently ICS doesn't let you do that;
14615            instead, you immediately punch your clock after making
14616            a move, but you can offer a draw at any time. */
14617
14618         SendToICS(ics_prefix);
14619         SendToICS("draw\n");
14620         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14621     } else if (cmailMsgLoaded) {
14622         if (currentMove == cmailOldMove &&
14623             commentList[cmailOldMove] != NULL &&
14624             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14625                    "Black offers a draw" : "White offers a draw")) {
14626             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14627             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14628         } else if (currentMove == cmailOldMove + 1) {
14629             char *offer = WhiteOnMove(cmailOldMove) ?
14630               "White offers a draw" : "Black offers a draw";
14631             AppendComment(currentMove, offer, TRUE);
14632             DisplayComment(currentMove - 1, offer);
14633             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14634         } else {
14635             DisplayError(_("You must make your move before offering a draw"), 0);
14636             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14637         }
14638     } else if (first.offeredDraw) {
14639         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14640     } else {
14641         if (first.sendDrawOffers) {
14642             SendToProgram("draw\n", &first);
14643             userOfferedDraw = TRUE;
14644         }
14645     }
14646 }
14647
14648 void
14649 AdjournEvent ()
14650 {
14651     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14652
14653     if (appData.icsActive) {
14654         SendToICS(ics_prefix);
14655         SendToICS("adjourn\n");
14656     } else {
14657         /* Currently GNU Chess doesn't offer or accept Adjourns */
14658     }
14659 }
14660
14661
14662 void
14663 AbortEvent ()
14664 {
14665     /* Offer Abort or accept pending Abort offer from opponent */
14666
14667     if (appData.icsActive) {
14668         SendToICS(ics_prefix);
14669         SendToICS("abort\n");
14670     } else {
14671         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14672     }
14673 }
14674
14675 void
14676 ResignEvent ()
14677 {
14678     /* Resign.  You can do this even if it's not your turn. */
14679
14680     if (appData.icsActive) {
14681         SendToICS(ics_prefix);
14682         SendToICS("resign\n");
14683     } else {
14684         switch (gameMode) {
14685           case MachinePlaysWhite:
14686             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14687             break;
14688           case MachinePlaysBlack:
14689             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14690             break;
14691           case EditGame:
14692             if (cmailMsgLoaded) {
14693                 TruncateGame();
14694                 if (WhiteOnMove(cmailOldMove)) {
14695                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14696                 } else {
14697                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14698                 }
14699                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14700             }
14701             break;
14702           default:
14703             break;
14704         }
14705     }
14706 }
14707
14708
14709 void
14710 StopObservingEvent ()
14711 {
14712     /* Stop observing current games */
14713     SendToICS(ics_prefix);
14714     SendToICS("unobserve\n");
14715 }
14716
14717 void
14718 StopExaminingEvent ()
14719 {
14720     /* Stop observing current game */
14721     SendToICS(ics_prefix);
14722     SendToICS("unexamine\n");
14723 }
14724
14725 void
14726 ForwardInner (int target)
14727 {
14728     int limit; int oldSeekGraphUp = seekGraphUp;
14729
14730     if (appData.debugMode)
14731         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14732                 target, currentMove, forwardMostMove);
14733
14734     if (gameMode == EditPosition)
14735       return;
14736
14737     seekGraphUp = FALSE;
14738     MarkTargetSquares(1);
14739
14740     if (gameMode == PlayFromGameFile && !pausing)
14741       PauseEvent();
14742
14743     if (gameMode == IcsExamining && pausing)
14744       limit = pauseExamForwardMostMove;
14745     else
14746       limit = forwardMostMove;
14747
14748     if (target > limit) target = limit;
14749
14750     if (target > 0 && moveList[target - 1][0]) {
14751         int fromX, fromY, toX, toY;
14752         toX = moveList[target - 1][2] - AAA;
14753         toY = moveList[target - 1][3] - ONE;
14754         if (moveList[target - 1][1] == '@') {
14755             if (appData.highlightLastMove) {
14756                 SetHighlights(-1, -1, toX, toY);
14757             }
14758         } else {
14759             fromX = moveList[target - 1][0] - AAA;
14760             fromY = moveList[target - 1][1] - ONE;
14761             if (target == currentMove + 1) {
14762                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14763             }
14764             if (appData.highlightLastMove) {
14765                 SetHighlights(fromX, fromY, toX, toY);
14766             }
14767         }
14768     }
14769     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14770         gameMode == Training || gameMode == PlayFromGameFile ||
14771         gameMode == AnalyzeFile) {
14772         while (currentMove < target) {
14773             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14774             SendMoveToProgram(currentMove++, &first);
14775         }
14776     } else {
14777         currentMove = target;
14778     }
14779
14780     if (gameMode == EditGame || gameMode == EndOfGame) {
14781         whiteTimeRemaining = timeRemaining[0][currentMove];
14782         blackTimeRemaining = timeRemaining[1][currentMove];
14783     }
14784     DisplayBothClocks();
14785     DisplayMove(currentMove - 1);
14786     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14787     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14788     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14789         DisplayComment(currentMove - 1, commentList[currentMove]);
14790     }
14791     ClearMap(); // [HGM] exclude: invalidate map
14792 }
14793
14794
14795 void
14796 ForwardEvent ()
14797 {
14798     if (gameMode == IcsExamining && !pausing) {
14799         SendToICS(ics_prefix);
14800         SendToICS("forward\n");
14801     } else {
14802         ForwardInner(currentMove + 1);
14803     }
14804 }
14805
14806 void
14807 ToEndEvent ()
14808 {
14809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14810         /* to optimze, we temporarily turn off analysis mode while we feed
14811          * the remaining moves to the engine. Otherwise we get analysis output
14812          * after each move.
14813          */
14814         if (first.analysisSupport) {
14815           SendToProgram("exit\nforce\n", &first);
14816           first.analyzing = FALSE;
14817         }
14818     }
14819
14820     if (gameMode == IcsExamining && !pausing) {
14821         SendToICS(ics_prefix);
14822         SendToICS("forward 999999\n");
14823     } else {
14824         ForwardInner(forwardMostMove);
14825     }
14826
14827     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14828         /* we have fed all the moves, so reactivate analysis mode */
14829         SendToProgram("analyze\n", &first);
14830         first.analyzing = TRUE;
14831         /*first.maybeThinking = TRUE;*/
14832         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14833     }
14834 }
14835
14836 void
14837 BackwardInner (int target)
14838 {
14839     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14840
14841     if (appData.debugMode)
14842         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14843                 target, currentMove, forwardMostMove);
14844
14845     if (gameMode == EditPosition) return;
14846     seekGraphUp = FALSE;
14847     MarkTargetSquares(1);
14848     if (currentMove <= backwardMostMove) {
14849         ClearHighlights();
14850         DrawPosition(full_redraw, boards[currentMove]);
14851         return;
14852     }
14853     if (gameMode == PlayFromGameFile && !pausing)
14854       PauseEvent();
14855
14856     if (moveList[target][0]) {
14857         int fromX, fromY, toX, toY;
14858         toX = moveList[target][2] - AAA;
14859         toY = moveList[target][3] - ONE;
14860         if (moveList[target][1] == '@') {
14861             if (appData.highlightLastMove) {
14862                 SetHighlights(-1, -1, toX, toY);
14863             }
14864         } else {
14865             fromX = moveList[target][0] - AAA;
14866             fromY = moveList[target][1] - ONE;
14867             if (target == currentMove - 1) {
14868                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14869             }
14870             if (appData.highlightLastMove) {
14871                 SetHighlights(fromX, fromY, toX, toY);
14872             }
14873         }
14874     }
14875     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14876         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14877         while (currentMove > target) {
14878             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14879                 // null move cannot be undone. Reload program with move history before it.
14880                 int i;
14881                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14882                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14883                 }
14884                 SendBoard(&first, i); 
14885               if(second.analyzing) SendBoard(&second, i);
14886                 for(currentMove=i; currentMove<target; currentMove++) {
14887                     SendMoveToProgram(currentMove, &first);
14888                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14889                 }
14890                 break;
14891             }
14892             SendToBoth("undo\n");
14893             currentMove--;
14894         }
14895     } else {
14896         currentMove = target;
14897     }
14898
14899     if (gameMode == EditGame || gameMode == EndOfGame) {
14900         whiteTimeRemaining = timeRemaining[0][currentMove];
14901         blackTimeRemaining = timeRemaining[1][currentMove];
14902     }
14903     DisplayBothClocks();
14904     DisplayMove(currentMove - 1);
14905     DrawPosition(full_redraw, boards[currentMove]);
14906     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14907     // [HGM] PV info: routine tests if comment empty
14908     DisplayComment(currentMove - 1, commentList[currentMove]);
14909     ClearMap(); // [HGM] exclude: invalidate map
14910 }
14911
14912 void
14913 BackwardEvent ()
14914 {
14915     if (gameMode == IcsExamining && !pausing) {
14916         SendToICS(ics_prefix);
14917         SendToICS("backward\n");
14918     } else {
14919         BackwardInner(currentMove - 1);
14920     }
14921 }
14922
14923 void
14924 ToStartEvent ()
14925 {
14926     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14927         /* to optimize, we temporarily turn off analysis mode while we undo
14928          * all the moves. Otherwise we get analysis output after each undo.
14929          */
14930         if (first.analysisSupport) {
14931           SendToProgram("exit\nforce\n", &first);
14932           first.analyzing = FALSE;
14933         }
14934     }
14935
14936     if (gameMode == IcsExamining && !pausing) {
14937         SendToICS(ics_prefix);
14938         SendToICS("backward 999999\n");
14939     } else {
14940         BackwardInner(backwardMostMove);
14941     }
14942
14943     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14944         /* we have fed all the moves, so reactivate analysis mode */
14945         SendToProgram("analyze\n", &first);
14946         first.analyzing = TRUE;
14947         /*first.maybeThinking = TRUE;*/
14948         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14949     }
14950 }
14951
14952 void
14953 ToNrEvent (int to)
14954 {
14955   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14956   if (to >= forwardMostMove) to = forwardMostMove;
14957   if (to <= backwardMostMove) to = backwardMostMove;
14958   if (to < currentMove) {
14959     BackwardInner(to);
14960   } else {
14961     ForwardInner(to);
14962   }
14963 }
14964
14965 void
14966 RevertEvent (Boolean annotate)
14967 {
14968     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14969         return;
14970     }
14971     if (gameMode != IcsExamining) {
14972         DisplayError(_("You are not examining a game"), 0);
14973         return;
14974     }
14975     if (pausing) {
14976         DisplayError(_("You can't revert while pausing"), 0);
14977         return;
14978     }
14979     SendToICS(ics_prefix);
14980     SendToICS("revert\n");
14981 }
14982
14983 void
14984 RetractMoveEvent ()
14985 {
14986     switch (gameMode) {
14987       case MachinePlaysWhite:
14988       case MachinePlaysBlack:
14989         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14990             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14991             return;
14992         }
14993         if (forwardMostMove < 2) return;
14994         currentMove = forwardMostMove = forwardMostMove - 2;
14995         whiteTimeRemaining = timeRemaining[0][currentMove];
14996         blackTimeRemaining = timeRemaining[1][currentMove];
14997         DisplayBothClocks();
14998         DisplayMove(currentMove - 1);
14999         ClearHighlights();/*!! could figure this out*/
15000         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15001         SendToProgram("remove\n", &first);
15002         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15003         break;
15004
15005       case BeginningOfGame:
15006       default:
15007         break;
15008
15009       case IcsPlayingWhite:
15010       case IcsPlayingBlack:
15011         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15012             SendToICS(ics_prefix);
15013             SendToICS("takeback 2\n");
15014         } else {
15015             SendToICS(ics_prefix);
15016             SendToICS("takeback 1\n");
15017         }
15018         break;
15019     }
15020 }
15021
15022 void
15023 MoveNowEvent ()
15024 {
15025     ChessProgramState *cps;
15026
15027     switch (gameMode) {
15028       case MachinePlaysWhite:
15029         if (!WhiteOnMove(forwardMostMove)) {
15030             DisplayError(_("It is your turn"), 0);
15031             return;
15032         }
15033         cps = &first;
15034         break;
15035       case MachinePlaysBlack:
15036         if (WhiteOnMove(forwardMostMove)) {
15037             DisplayError(_("It is your turn"), 0);
15038             return;
15039         }
15040         cps = &first;
15041         break;
15042       case TwoMachinesPlay:
15043         if (WhiteOnMove(forwardMostMove) ==
15044             (first.twoMachinesColor[0] == 'w')) {
15045             cps = &first;
15046         } else {
15047             cps = &second;
15048         }
15049         break;
15050       case BeginningOfGame:
15051       default:
15052         return;
15053     }
15054     SendToProgram("?\n", cps);
15055 }
15056
15057 void
15058 TruncateGameEvent ()
15059 {
15060     EditGameEvent();
15061     if (gameMode != EditGame) return;
15062     TruncateGame();
15063 }
15064
15065 void
15066 TruncateGame ()
15067 {
15068     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15069     if (forwardMostMove > currentMove) {
15070         if (gameInfo.resultDetails != NULL) {
15071             free(gameInfo.resultDetails);
15072             gameInfo.resultDetails = NULL;
15073             gameInfo.result = GameUnfinished;
15074         }
15075         forwardMostMove = currentMove;
15076         HistorySet(parseList, backwardMostMove, forwardMostMove,
15077                    currentMove-1);
15078     }
15079 }
15080
15081 void
15082 HintEvent ()
15083 {
15084     if (appData.noChessProgram) return;
15085     switch (gameMode) {
15086       case MachinePlaysWhite:
15087         if (WhiteOnMove(forwardMostMove)) {
15088             DisplayError(_("Wait until your turn"), 0);
15089             return;
15090         }
15091         break;
15092       case BeginningOfGame:
15093       case MachinePlaysBlack:
15094         if (!WhiteOnMove(forwardMostMove)) {
15095             DisplayError(_("Wait until your turn"), 0);
15096             return;
15097         }
15098         break;
15099       default:
15100         DisplayError(_("No hint available"), 0);
15101         return;
15102     }
15103     SendToProgram("hint\n", &first);
15104     hintRequested = TRUE;
15105 }
15106
15107 void
15108 CreateBookEvent ()
15109 {
15110     ListGame * lg = (ListGame *) gameList.head;
15111     FILE *f;
15112     int nItem;
15113     static int secondTime = FALSE;
15114
15115     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15116         DisplayError(_("Game list not loaded or empty"), 0);
15117         return;
15118     }
15119
15120     if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
15121         fclose(f);
15122         secondTime++;
15123         DisplayNote(_("Book file exists! Try again for overwrite."));
15124         return;
15125     }
15126
15127     creatingBook = TRUE;
15128     secondTime = FALSE;
15129
15130     /* Get list size */
15131     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15132         LoadGame(f, nItem, "", TRUE);
15133         AddGameToBook(TRUE);
15134         lg = (ListGame *) lg->node.succ;
15135     }
15136
15137     creatingBook = FALSE;
15138     FlushBook();
15139 }
15140
15141 void
15142 BookEvent ()
15143 {
15144     if (appData.noChessProgram) return;
15145     switch (gameMode) {
15146       case MachinePlaysWhite:
15147         if (WhiteOnMove(forwardMostMove)) {
15148             DisplayError(_("Wait until your turn"), 0);
15149             return;
15150         }
15151         break;
15152       case BeginningOfGame:
15153       case MachinePlaysBlack:
15154         if (!WhiteOnMove(forwardMostMove)) {
15155             DisplayError(_("Wait until your turn"), 0);
15156             return;
15157         }
15158         break;
15159       case EditPosition:
15160         EditPositionDone(TRUE);
15161         break;
15162       case TwoMachinesPlay:
15163         return;
15164       default:
15165         break;
15166     }
15167     SendToProgram("bk\n", &first);
15168     bookOutput[0] = NULLCHAR;
15169     bookRequested = TRUE;
15170 }
15171
15172 void
15173 AboutGameEvent ()
15174 {
15175     char *tags = PGNTags(&gameInfo);
15176     TagsPopUp(tags, CmailMsg());
15177     free(tags);
15178 }
15179
15180 /* end button procedures */
15181
15182 void
15183 PrintPosition (FILE *fp, int move)
15184 {
15185     int i, j;
15186
15187     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15188         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15189             char c = PieceToChar(boards[move][i][j]);
15190             fputc(c == 'x' ? '.' : c, fp);
15191             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15192         }
15193     }
15194     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15195       fprintf(fp, "white to play\n");
15196     else
15197       fprintf(fp, "black to play\n");
15198 }
15199
15200 void
15201 PrintOpponents (FILE *fp)
15202 {
15203     if (gameInfo.white != NULL) {
15204         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15205     } else {
15206         fprintf(fp, "\n");
15207     }
15208 }
15209
15210 /* Find last component of program's own name, using some heuristics */
15211 void
15212 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15213 {
15214     char *p, *q, c;
15215     int local = (strcmp(host, "localhost") == 0);
15216     while (!local && (p = strchr(prog, ';')) != NULL) {
15217         p++;
15218         while (*p == ' ') p++;
15219         prog = p;
15220     }
15221     if (*prog == '"' || *prog == '\'') {
15222         q = strchr(prog + 1, *prog);
15223     } else {
15224         q = strchr(prog, ' ');
15225     }
15226     if (q == NULL) q = prog + strlen(prog);
15227     p = q;
15228     while (p >= prog && *p != '/' && *p != '\\') p--;
15229     p++;
15230     if(p == prog && *p == '"') p++;
15231     c = *q; *q = 0;
15232     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15233     memcpy(buf, p, q - p);
15234     buf[q - p] = NULLCHAR;
15235     if (!local) {
15236         strcat(buf, "@");
15237         strcat(buf, host);
15238     }
15239 }
15240
15241 char *
15242 TimeControlTagValue ()
15243 {
15244     char buf[MSG_SIZ];
15245     if (!appData.clockMode) {
15246       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15247     } else if (movesPerSession > 0) {
15248       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15249     } else if (timeIncrement == 0) {
15250       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15251     } else {
15252       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15253     }
15254     return StrSave(buf);
15255 }
15256
15257 void
15258 SetGameInfo ()
15259 {
15260     /* This routine is used only for certain modes */
15261     VariantClass v = gameInfo.variant;
15262     ChessMove r = GameUnfinished;
15263     char *p = NULL;
15264
15265     if(keepInfo) return;
15266
15267     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15268         r = gameInfo.result;
15269         p = gameInfo.resultDetails;
15270         gameInfo.resultDetails = NULL;
15271     }
15272     ClearGameInfo(&gameInfo);
15273     gameInfo.variant = v;
15274
15275     switch (gameMode) {
15276       case MachinePlaysWhite:
15277         gameInfo.event = StrSave( appData.pgnEventHeader );
15278         gameInfo.site = StrSave(HostName());
15279         gameInfo.date = PGNDate();
15280         gameInfo.round = StrSave("-");
15281         gameInfo.white = StrSave(first.tidy);
15282         gameInfo.black = StrSave(UserName());
15283         gameInfo.timeControl = TimeControlTagValue();
15284         break;
15285
15286       case MachinePlaysBlack:
15287         gameInfo.event = StrSave( appData.pgnEventHeader );
15288         gameInfo.site = StrSave(HostName());
15289         gameInfo.date = PGNDate();
15290         gameInfo.round = StrSave("-");
15291         gameInfo.white = StrSave(UserName());
15292         gameInfo.black = StrSave(first.tidy);
15293         gameInfo.timeControl = TimeControlTagValue();
15294         break;
15295
15296       case TwoMachinesPlay:
15297         gameInfo.event = StrSave( appData.pgnEventHeader );
15298         gameInfo.site = StrSave(HostName());
15299         gameInfo.date = PGNDate();
15300         if (roundNr > 0) {
15301             char buf[MSG_SIZ];
15302             snprintf(buf, MSG_SIZ, "%d", roundNr);
15303             gameInfo.round = StrSave(buf);
15304         } else {
15305             gameInfo.round = StrSave("-");
15306         }
15307         if (first.twoMachinesColor[0] == 'w') {
15308             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15309             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15310         } else {
15311             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15312             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15313         }
15314         gameInfo.timeControl = TimeControlTagValue();
15315         break;
15316
15317       case EditGame:
15318         gameInfo.event = StrSave("Edited game");
15319         gameInfo.site = StrSave(HostName());
15320         gameInfo.date = PGNDate();
15321         gameInfo.round = StrSave("-");
15322         gameInfo.white = StrSave("-");
15323         gameInfo.black = StrSave("-");
15324         gameInfo.result = r;
15325         gameInfo.resultDetails = p;
15326         break;
15327
15328       case EditPosition:
15329         gameInfo.event = StrSave("Edited position");
15330         gameInfo.site = StrSave(HostName());
15331         gameInfo.date = PGNDate();
15332         gameInfo.round = StrSave("-");
15333         gameInfo.white = StrSave("-");
15334         gameInfo.black = StrSave("-");
15335         break;
15336
15337       case IcsPlayingWhite:
15338       case IcsPlayingBlack:
15339       case IcsObserving:
15340       case IcsExamining:
15341         break;
15342
15343       case PlayFromGameFile:
15344         gameInfo.event = StrSave("Game from non-PGN file");
15345         gameInfo.site = StrSave(HostName());
15346         gameInfo.date = PGNDate();
15347         gameInfo.round = StrSave("-");
15348         gameInfo.white = StrSave("?");
15349         gameInfo.black = StrSave("?");
15350         break;
15351
15352       default:
15353         break;
15354     }
15355 }
15356
15357 void
15358 ReplaceComment (int index, char *text)
15359 {
15360     int len;
15361     char *p;
15362     float score;
15363
15364     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15365        pvInfoList[index-1].depth == len &&
15366        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15367        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15368     while (*text == '\n') text++;
15369     len = strlen(text);
15370     while (len > 0 && text[len - 1] == '\n') len--;
15371
15372     if (commentList[index] != NULL)
15373       free(commentList[index]);
15374
15375     if (len == 0) {
15376         commentList[index] = NULL;
15377         return;
15378     }
15379   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15380       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15381       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15382     commentList[index] = (char *) malloc(len + 2);
15383     strncpy(commentList[index], text, len);
15384     commentList[index][len] = '\n';
15385     commentList[index][len + 1] = NULLCHAR;
15386   } else {
15387     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15388     char *p;
15389     commentList[index] = (char *) malloc(len + 7);
15390     safeStrCpy(commentList[index], "{\n", 3);
15391     safeStrCpy(commentList[index]+2, text, len+1);
15392     commentList[index][len+2] = NULLCHAR;
15393     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15394     strcat(commentList[index], "\n}\n");
15395   }
15396 }
15397
15398 void
15399 CrushCRs (char *text)
15400 {
15401   char *p = text;
15402   char *q = text;
15403   char ch;
15404
15405   do {
15406     ch = *p++;
15407     if (ch == '\r') continue;
15408     *q++ = ch;
15409   } while (ch != '\0');
15410 }
15411
15412 void
15413 AppendComment (int index, char *text, Boolean addBraces)
15414 /* addBraces  tells if we should add {} */
15415 {
15416     int oldlen, len;
15417     char *old;
15418
15419 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15420     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15421
15422     CrushCRs(text);
15423     while (*text == '\n') text++;
15424     len = strlen(text);
15425     while (len > 0 && text[len - 1] == '\n') len--;
15426     text[len] = NULLCHAR;
15427
15428     if (len == 0) return;
15429
15430     if (commentList[index] != NULL) {
15431       Boolean addClosingBrace = addBraces;
15432         old = commentList[index];
15433         oldlen = strlen(old);
15434         while(commentList[index][oldlen-1] ==  '\n')
15435           commentList[index][--oldlen] = NULLCHAR;
15436         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15437         safeStrCpy(commentList[index], old, oldlen + len + 6);
15438         free(old);
15439         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15440         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15441           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15442           while (*text == '\n') { text++; len--; }
15443           commentList[index][--oldlen] = NULLCHAR;
15444       }
15445         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15446         else          strcat(commentList[index], "\n");
15447         strcat(commentList[index], text);
15448         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15449         else          strcat(commentList[index], "\n");
15450     } else {
15451         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15452         if(addBraces)
15453           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15454         else commentList[index][0] = NULLCHAR;
15455         strcat(commentList[index], text);
15456         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15457         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15458     }
15459 }
15460
15461 static char *
15462 FindStr (char * text, char * sub_text)
15463 {
15464     char * result = strstr( text, sub_text );
15465
15466     if( result != NULL ) {
15467         result += strlen( sub_text );
15468     }
15469
15470     return result;
15471 }
15472
15473 /* [AS] Try to extract PV info from PGN comment */
15474 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15475 char *
15476 GetInfoFromComment (int index, char * text)
15477 {
15478     char * sep = text, *p;
15479
15480     if( text != NULL && index > 0 ) {
15481         int score = 0;
15482         int depth = 0;
15483         int time = -1, sec = 0, deci;
15484         char * s_eval = FindStr( text, "[%eval " );
15485         char * s_emt = FindStr( text, "[%emt " );
15486
15487         if( s_eval != NULL || s_emt != NULL ) {
15488             /* New style */
15489             char delim;
15490
15491             if( s_eval != NULL ) {
15492                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15493                     return text;
15494                 }
15495
15496                 if( delim != ']' ) {
15497                     return text;
15498                 }
15499             }
15500
15501             if( s_emt != NULL ) {
15502             }
15503                 return text;
15504         }
15505         else {
15506             /* We expect something like: [+|-]nnn.nn/dd */
15507             int score_lo = 0;
15508
15509             if(*text != '{') return text; // [HGM] braces: must be normal comment
15510
15511             sep = strchr( text, '/' );
15512             if( sep == NULL || sep < (text+4) ) {
15513                 return text;
15514             }
15515
15516             p = text;
15517             if(p[1] == '(') { // comment starts with PV
15518                p = strchr(p, ')'); // locate end of PV
15519                if(p == NULL || sep < p+5) return text;
15520                // at this point we have something like "{(.*) +0.23/6 ..."
15521                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15522                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15523                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15524             }
15525             time = -1; sec = -1; deci = -1;
15526             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15527                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15528                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15529                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15530                 return text;
15531             }
15532
15533             if( score_lo < 0 || score_lo >= 100 ) {
15534                 return text;
15535             }
15536
15537             if(sec >= 0) time = 600*time + 10*sec; else
15538             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15539
15540             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15541
15542             /* [HGM] PV time: now locate end of PV info */
15543             while( *++sep >= '0' && *sep <= '9'); // strip depth
15544             if(time >= 0)
15545             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15546             if(sec >= 0)
15547             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15548             if(deci >= 0)
15549             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15550             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15551         }
15552
15553         if( depth <= 0 ) {
15554             return text;
15555         }
15556
15557         if( time < 0 ) {
15558             time = -1;
15559         }
15560
15561         pvInfoList[index-1].depth = depth;
15562         pvInfoList[index-1].score = score;
15563         pvInfoList[index-1].time  = 10*time; // centi-sec
15564         if(*sep == '}') *sep = 0; else *--sep = '{';
15565         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15566     }
15567     return sep;
15568 }
15569
15570 void
15571 SendToProgram (char *message, ChessProgramState *cps)
15572 {
15573     int count, outCount, error;
15574     char buf[MSG_SIZ];
15575
15576     if (cps->pr == NoProc) return;
15577     Attention(cps);
15578
15579     if (appData.debugMode) {
15580         TimeMark now;
15581         GetTimeMark(&now);
15582         fprintf(debugFP, "%ld >%-6s: %s",
15583                 SubtractTimeMarks(&now, &programStartTime),
15584                 cps->which, message);
15585         if(serverFP)
15586             fprintf(serverFP, "%ld >%-6s: %s",
15587                 SubtractTimeMarks(&now, &programStartTime),
15588                 cps->which, message), fflush(serverFP);
15589     }
15590
15591     count = strlen(message);
15592     outCount = OutputToProcess(cps->pr, message, count, &error);
15593     if (outCount < count && !exiting
15594                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15595       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15596       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15597         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15598             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15599                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15600                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15601                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15602             } else {
15603                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15604                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15605                 gameInfo.result = res;
15606             }
15607             gameInfo.resultDetails = StrSave(buf);
15608         }
15609         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15610         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15611     }
15612 }
15613
15614 void
15615 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15616 {
15617     char *end_str;
15618     char buf[MSG_SIZ];
15619     ChessProgramState *cps = (ChessProgramState *)closure;
15620
15621     if (isr != cps->isr) return; /* Killed intentionally */
15622     if (count <= 0) {
15623         if (count == 0) {
15624             RemoveInputSource(cps->isr);
15625             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15626                     _(cps->which), cps->program);
15627             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15628             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15629                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15630                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15631                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15632                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15633                 } else {
15634                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15635                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15636                     gameInfo.result = res;
15637                 }
15638                 gameInfo.resultDetails = StrSave(buf);
15639             }
15640             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15641             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15642         } else {
15643             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15644                     _(cps->which), cps->program);
15645             RemoveInputSource(cps->isr);
15646
15647             /* [AS] Program is misbehaving badly... kill it */
15648             if( count == -2 ) {
15649                 DestroyChildProcess( cps->pr, 9 );
15650                 cps->pr = NoProc;
15651             }
15652
15653             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15654         }
15655         return;
15656     }
15657
15658     if ((end_str = strchr(message, '\r')) != NULL)
15659       *end_str = NULLCHAR;
15660     if ((end_str = strchr(message, '\n')) != NULL)
15661       *end_str = NULLCHAR;
15662
15663     if (appData.debugMode) {
15664         TimeMark now; int print = 1;
15665         char *quote = ""; char c; int i;
15666
15667         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15668                 char start = message[0];
15669                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15670                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15671                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15672                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15673                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15674                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15675                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15676                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15677                    sscanf(message, "hint: %c", &c)!=1 && 
15678                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15679                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15680                     print = (appData.engineComments >= 2);
15681                 }
15682                 message[0] = start; // restore original message
15683         }
15684         if(print) {
15685                 GetTimeMark(&now);
15686                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15687                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15688                         quote,
15689                         message);
15690                 if(serverFP)
15691                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15692                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15693                         quote,
15694                         message), fflush(serverFP);
15695         }
15696     }
15697
15698     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15699     if (appData.icsEngineAnalyze) {
15700         if (strstr(message, "whisper") != NULL ||
15701              strstr(message, "kibitz") != NULL ||
15702             strstr(message, "tellics") != NULL) return;
15703     }
15704
15705     HandleMachineMove(message, cps);
15706 }
15707
15708
15709 void
15710 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15711 {
15712     char buf[MSG_SIZ];
15713     int seconds;
15714
15715     if( timeControl_2 > 0 ) {
15716         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15717             tc = timeControl_2;
15718         }
15719     }
15720     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15721     inc /= cps->timeOdds;
15722     st  /= cps->timeOdds;
15723
15724     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15725
15726     if (st > 0) {
15727       /* Set exact time per move, normally using st command */
15728       if (cps->stKludge) {
15729         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15730         seconds = st % 60;
15731         if (seconds == 0) {
15732           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15733         } else {
15734           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15735         }
15736       } else {
15737         snprintf(buf, MSG_SIZ, "st %d\n", st);
15738       }
15739     } else {
15740       /* Set conventional or incremental time control, using level command */
15741       if (seconds == 0) {
15742         /* Note old gnuchess bug -- minutes:seconds used to not work.
15743            Fixed in later versions, but still avoid :seconds
15744            when seconds is 0. */
15745         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15746       } else {
15747         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15748                  seconds, inc/1000.);
15749       }
15750     }
15751     SendToProgram(buf, cps);
15752
15753     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15754     /* Orthogonally, limit search to given depth */
15755     if (sd > 0) {
15756       if (cps->sdKludge) {
15757         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15758       } else {
15759         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15760       }
15761       SendToProgram(buf, cps);
15762     }
15763
15764     if(cps->nps >= 0) { /* [HGM] nps */
15765         if(cps->supportsNPS == FALSE)
15766           cps->nps = -1; // don't use if engine explicitly says not supported!
15767         else {
15768           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15769           SendToProgram(buf, cps);
15770         }
15771     }
15772 }
15773
15774 ChessProgramState *
15775 WhitePlayer ()
15776 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15777 {
15778     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15779        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15780         return &second;
15781     return &first;
15782 }
15783
15784 void
15785 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15786 {
15787     char message[MSG_SIZ];
15788     long time, otime;
15789
15790     /* Note: this routine must be called when the clocks are stopped
15791        or when they have *just* been set or switched; otherwise
15792        it will be off by the time since the current tick started.
15793     */
15794     if (machineWhite) {
15795         time = whiteTimeRemaining / 10;
15796         otime = blackTimeRemaining / 10;
15797     } else {
15798         time = blackTimeRemaining / 10;
15799         otime = whiteTimeRemaining / 10;
15800     }
15801     /* [HGM] translate opponent's time by time-odds factor */
15802     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15803
15804     if (time <= 0) time = 1;
15805     if (otime <= 0) otime = 1;
15806
15807     snprintf(message, MSG_SIZ, "time %ld\n", time);
15808     SendToProgram(message, cps);
15809
15810     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15811     SendToProgram(message, cps);
15812 }
15813
15814 int
15815 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15816 {
15817   char buf[MSG_SIZ];
15818   int len = strlen(name);
15819   int val;
15820
15821   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15822     (*p) += len + 1;
15823     sscanf(*p, "%d", &val);
15824     *loc = (val != 0);
15825     while (**p && **p != ' ')
15826       (*p)++;
15827     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15828     SendToProgram(buf, cps);
15829     return TRUE;
15830   }
15831   return FALSE;
15832 }
15833
15834 int
15835 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15836 {
15837   char buf[MSG_SIZ];
15838   int len = strlen(name);
15839   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15840     (*p) += len + 1;
15841     sscanf(*p, "%d", loc);
15842     while (**p && **p != ' ') (*p)++;
15843     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15844     SendToProgram(buf, cps);
15845     return TRUE;
15846   }
15847   return FALSE;
15848 }
15849
15850 int
15851 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15852 {
15853   char buf[MSG_SIZ];
15854   int len = strlen(name);
15855   if (strncmp((*p), name, len) == 0
15856       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15857     (*p) += len + 2;
15858     sscanf(*p, "%[^\"]", loc);
15859     while (**p && **p != '\"') (*p)++;
15860     if (**p == '\"') (*p)++;
15861     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15862     SendToProgram(buf, cps);
15863     return TRUE;
15864   }
15865   return FALSE;
15866 }
15867
15868 int
15869 ParseOption (Option *opt, ChessProgramState *cps)
15870 // [HGM] options: process the string that defines an engine option, and determine
15871 // name, type, default value, and allowed value range
15872 {
15873         char *p, *q, buf[MSG_SIZ];
15874         int n, min = (-1)<<31, max = 1<<31, def;
15875
15876         if(p = strstr(opt->name, " -spin ")) {
15877             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15878             if(max < min) max = min; // enforce consistency
15879             if(def < min) def = min;
15880             if(def > max) def = max;
15881             opt->value = def;
15882             opt->min = min;
15883             opt->max = max;
15884             opt->type = Spin;
15885         } else if((p = strstr(opt->name, " -slider "))) {
15886             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15887             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15888             if(max < min) max = min; // enforce consistency
15889             if(def < min) def = min;
15890             if(def > max) def = max;
15891             opt->value = def;
15892             opt->min = min;
15893             opt->max = max;
15894             opt->type = Spin; // Slider;
15895         } else if((p = strstr(opt->name, " -string "))) {
15896             opt->textValue = p+9;
15897             opt->type = TextBox;
15898         } else if((p = strstr(opt->name, " -file "))) {
15899             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15900             opt->textValue = p+7;
15901             opt->type = FileName; // FileName;
15902         } else if((p = strstr(opt->name, " -path "))) {
15903             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15904             opt->textValue = p+7;
15905             opt->type = PathName; // PathName;
15906         } else if(p = strstr(opt->name, " -check ")) {
15907             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15908             opt->value = (def != 0);
15909             opt->type = CheckBox;
15910         } else if(p = strstr(opt->name, " -combo ")) {
15911             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15912             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15913             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15914             opt->value = n = 0;
15915             while(q = StrStr(q, " /// ")) {
15916                 n++; *q = 0;    // count choices, and null-terminate each of them
15917                 q += 5;
15918                 if(*q == '*') { // remember default, which is marked with * prefix
15919                     q++;
15920                     opt->value = n;
15921                 }
15922                 cps->comboList[cps->comboCnt++] = q;
15923             }
15924             cps->comboList[cps->comboCnt++] = NULL;
15925             opt->max = n + 1;
15926             opt->type = ComboBox;
15927         } else if(p = strstr(opt->name, " -button")) {
15928             opt->type = Button;
15929         } else if(p = strstr(opt->name, " -save")) {
15930             opt->type = SaveButton;
15931         } else return FALSE;
15932         *p = 0; // terminate option name
15933         // now look if the command-line options define a setting for this engine option.
15934         if(cps->optionSettings && cps->optionSettings[0])
15935             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15936         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15937           snprintf(buf, MSG_SIZ, "option %s", p);
15938                 if(p = strstr(buf, ",")) *p = 0;
15939                 if(q = strchr(buf, '=')) switch(opt->type) {
15940                     case ComboBox:
15941                         for(n=0; n<opt->max; n++)
15942                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15943                         break;
15944                     case TextBox:
15945                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15946                         break;
15947                     case Spin:
15948                     case CheckBox:
15949                         opt->value = atoi(q+1);
15950                     default:
15951                         break;
15952                 }
15953                 strcat(buf, "\n");
15954                 SendToProgram(buf, cps);
15955         }
15956         return TRUE;
15957 }
15958
15959 void
15960 FeatureDone (ChessProgramState *cps, int val)
15961 {
15962   DelayedEventCallback cb = GetDelayedEvent();
15963   if ((cb == InitBackEnd3 && cps == &first) ||
15964       (cb == SettingsMenuIfReady && cps == &second) ||
15965       (cb == LoadEngine) ||
15966       (cb == TwoMachinesEventIfReady)) {
15967     CancelDelayedEvent();
15968     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15969   }
15970   cps->initDone = val;
15971 }
15972
15973 /* Parse feature command from engine */
15974 void
15975 ParseFeatures (char *args, ChessProgramState *cps)
15976 {
15977   char *p = args;
15978   char *q;
15979   int val;
15980   char buf[MSG_SIZ];
15981
15982   for (;;) {
15983     while (*p == ' ') p++;
15984     if (*p == NULLCHAR) return;
15985
15986     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15987     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15988     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15989     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15990     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15991     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15992     if (BoolFeature(&p, "reuse", &val, cps)) {
15993       /* Engine can disable reuse, but can't enable it if user said no */
15994       if (!val) cps->reuse = FALSE;
15995       continue;
15996     }
15997     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15998     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15999       if (gameMode == TwoMachinesPlay) {
16000         DisplayTwoMachinesTitle();
16001       } else {
16002         DisplayTitle("");
16003       }
16004       continue;
16005     }
16006     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
16007     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16008     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16009     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16010     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16011     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16012     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16013     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16014     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16015     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16016     if (IntFeature(&p, "done", &val, cps)) {
16017       FeatureDone(cps, val);
16018       continue;
16019     }
16020     /* Added by Tord: */
16021     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16022     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16023     /* End of additions by Tord */
16024
16025     /* [HGM] added features: */
16026     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16027     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16028     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16029     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16030     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16031     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
16032     if (StringFeature(&p, "option", buf, cps)) {
16033         FREE(cps->option[cps->nrOptions].name);
16034         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
16035         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
16036         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16037           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16038             SendToProgram(buf, cps);
16039             continue;
16040         }
16041         if(cps->nrOptions >= MAX_OPTIONS) {
16042             cps->nrOptions--;
16043             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16044             DisplayError(buf, 0);
16045         }
16046         continue;
16047     }
16048     /* End of additions by HGM */
16049
16050     /* unknown feature: complain and skip */
16051     q = p;
16052     while (*q && *q != '=') q++;
16053     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16054     SendToProgram(buf, cps);
16055     p = q;
16056     if (*p == '=') {
16057       p++;
16058       if (*p == '\"') {
16059         p++;
16060         while (*p && *p != '\"') p++;
16061         if (*p == '\"') p++;
16062       } else {
16063         while (*p && *p != ' ') p++;
16064       }
16065     }
16066   }
16067
16068 }
16069
16070 void
16071 PeriodicUpdatesEvent (int newState)
16072 {
16073     if (newState == appData.periodicUpdates)
16074       return;
16075
16076     appData.periodicUpdates=newState;
16077
16078     /* Display type changes, so update it now */
16079 //    DisplayAnalysis();
16080
16081     /* Get the ball rolling again... */
16082     if (newState) {
16083         AnalysisPeriodicEvent(1);
16084         StartAnalysisClock();
16085     }
16086 }
16087
16088 void
16089 PonderNextMoveEvent (int newState)
16090 {
16091     if (newState == appData.ponderNextMove) return;
16092     if (gameMode == EditPosition) EditPositionDone(TRUE);
16093     if (newState) {
16094         SendToProgram("hard\n", &first);
16095         if (gameMode == TwoMachinesPlay) {
16096             SendToProgram("hard\n", &second);
16097         }
16098     } else {
16099         SendToProgram("easy\n", &first);
16100         thinkOutput[0] = NULLCHAR;
16101         if (gameMode == TwoMachinesPlay) {
16102             SendToProgram("easy\n", &second);
16103         }
16104     }
16105     appData.ponderNextMove = newState;
16106 }
16107
16108 void
16109 NewSettingEvent (int option, int *feature, char *command, int value)
16110 {
16111     char buf[MSG_SIZ];
16112
16113     if (gameMode == EditPosition) EditPositionDone(TRUE);
16114     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16115     if(feature == NULL || *feature) SendToProgram(buf, &first);
16116     if (gameMode == TwoMachinesPlay) {
16117         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16118     }
16119 }
16120
16121 void
16122 ShowThinkingEvent ()
16123 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16124 {
16125     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16126     int newState = appData.showThinking
16127         // [HGM] thinking: other features now need thinking output as well
16128         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16129
16130     if (oldState == newState) return;
16131     oldState = newState;
16132     if (gameMode == EditPosition) EditPositionDone(TRUE);
16133     if (oldState) {
16134         SendToProgram("post\n", &first);
16135         if (gameMode == TwoMachinesPlay) {
16136             SendToProgram("post\n", &second);
16137         }
16138     } else {
16139         SendToProgram("nopost\n", &first);
16140         thinkOutput[0] = NULLCHAR;
16141         if (gameMode == TwoMachinesPlay) {
16142             SendToProgram("nopost\n", &second);
16143         }
16144     }
16145 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16146 }
16147
16148 void
16149 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16150 {
16151   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16152   if (pr == NoProc) return;
16153   AskQuestion(title, question, replyPrefix, pr);
16154 }
16155
16156 void
16157 TypeInEvent (char firstChar)
16158 {
16159     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
16160         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16161         gameMode == AnalyzeMode || gameMode == EditGame || 
16162         gameMode == EditPosition || gameMode == IcsExamining ||
16163         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16164         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16165                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16166                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16167         gameMode == Training) PopUpMoveDialog(firstChar);
16168 }
16169
16170 void
16171 TypeInDoneEvent (char *move)
16172 {
16173         Board board;
16174         int n, fromX, fromY, toX, toY;
16175         char promoChar;
16176         ChessMove moveType;
16177
16178         // [HGM] FENedit
16179         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16180                 EditPositionPasteFEN(move);
16181                 return;
16182         }
16183         // [HGM] movenum: allow move number to be typed in any mode
16184         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16185           ToNrEvent(2*n-1);
16186           return;
16187         }
16188         // undocumented kludge: allow command-line option to be typed in!
16189         // (potentially fatal, and does not implement the effect of the option.)
16190         // should only be used for options that are values on which future decisions will be made,
16191         // and definitely not on options that would be used during initialization.
16192         if(strstr(move, "!!! -") == move) {
16193             ParseArgsFromString(move+4);
16194             return;
16195         }
16196
16197       if (gameMode != EditGame && currentMove != forwardMostMove && 
16198         gameMode != Training) {
16199         DisplayMoveError(_("Displayed move is not current"));
16200       } else {
16201         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16202           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16203         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16204         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16205           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16206           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16207         } else {
16208           DisplayMoveError(_("Could not parse move"));
16209         }
16210       }
16211 }
16212
16213 void
16214 DisplayMove (int moveNumber)
16215 {
16216     char message[MSG_SIZ];
16217     char res[MSG_SIZ];
16218     char cpThinkOutput[MSG_SIZ];
16219
16220     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16221
16222     if (moveNumber == forwardMostMove - 1 ||
16223         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16224
16225         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16226
16227         if (strchr(cpThinkOutput, '\n')) {
16228             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16229         }
16230     } else {
16231         *cpThinkOutput = NULLCHAR;
16232     }
16233
16234     /* [AS] Hide thinking from human user */
16235     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16236         *cpThinkOutput = NULLCHAR;
16237         if( thinkOutput[0] != NULLCHAR ) {
16238             int i;
16239
16240             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16241                 cpThinkOutput[i] = '.';
16242             }
16243             cpThinkOutput[i] = NULLCHAR;
16244             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16245         }
16246     }
16247
16248     if (moveNumber == forwardMostMove - 1 &&
16249         gameInfo.resultDetails != NULL) {
16250         if (gameInfo.resultDetails[0] == NULLCHAR) {
16251           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16252         } else {
16253           snprintf(res, MSG_SIZ, " {%s} %s",
16254                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16255         }
16256     } else {
16257         res[0] = NULLCHAR;
16258     }
16259
16260     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16261         DisplayMessage(res, cpThinkOutput);
16262     } else {
16263       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16264                 WhiteOnMove(moveNumber) ? " " : ".. ",
16265                 parseList[moveNumber], res);
16266         DisplayMessage(message, cpThinkOutput);
16267     }
16268 }
16269
16270 void
16271 DisplayComment (int moveNumber, char *text)
16272 {
16273     char title[MSG_SIZ];
16274
16275     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16276       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16277     } else {
16278       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16279               WhiteOnMove(moveNumber) ? " " : ".. ",
16280               parseList[moveNumber]);
16281     }
16282     if (text != NULL && (appData.autoDisplayComment || commentUp))
16283         CommentPopUp(title, text);
16284 }
16285
16286 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16287  * might be busy thinking or pondering.  It can be omitted if your
16288  * gnuchess is configured to stop thinking immediately on any user
16289  * input.  However, that gnuchess feature depends on the FIONREAD
16290  * ioctl, which does not work properly on some flavors of Unix.
16291  */
16292 void
16293 Attention (ChessProgramState *cps)
16294 {
16295 #if ATTENTION
16296     if (!cps->useSigint) return;
16297     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16298     switch (gameMode) {
16299       case MachinePlaysWhite:
16300       case MachinePlaysBlack:
16301       case TwoMachinesPlay:
16302       case IcsPlayingWhite:
16303       case IcsPlayingBlack:
16304       case AnalyzeMode:
16305       case AnalyzeFile:
16306         /* Skip if we know it isn't thinking */
16307         if (!cps->maybeThinking) return;
16308         if (appData.debugMode)
16309           fprintf(debugFP, "Interrupting %s\n", cps->which);
16310         InterruptChildProcess(cps->pr);
16311         cps->maybeThinking = FALSE;
16312         break;
16313       default:
16314         break;
16315     }
16316 #endif /*ATTENTION*/
16317 }
16318
16319 int
16320 CheckFlags ()
16321 {
16322     if (whiteTimeRemaining <= 0) {
16323         if (!whiteFlag) {
16324             whiteFlag = TRUE;
16325             if (appData.icsActive) {
16326                 if (appData.autoCallFlag &&
16327                     gameMode == IcsPlayingBlack && !blackFlag) {
16328                   SendToICS(ics_prefix);
16329                   SendToICS("flag\n");
16330                 }
16331             } else {
16332                 if (blackFlag) {
16333                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16334                 } else {
16335                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16336                     if (appData.autoCallFlag) {
16337                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16338                         return TRUE;
16339                     }
16340                 }
16341             }
16342         }
16343     }
16344     if (blackTimeRemaining <= 0) {
16345         if (!blackFlag) {
16346             blackFlag = TRUE;
16347             if (appData.icsActive) {
16348                 if (appData.autoCallFlag &&
16349                     gameMode == IcsPlayingWhite && !whiteFlag) {
16350                   SendToICS(ics_prefix);
16351                   SendToICS("flag\n");
16352                 }
16353             } else {
16354                 if (whiteFlag) {
16355                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16356                 } else {
16357                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16358                     if (appData.autoCallFlag) {
16359                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16360                         return TRUE;
16361                     }
16362                 }
16363             }
16364         }
16365     }
16366     return FALSE;
16367 }
16368
16369 void
16370 CheckTimeControl ()
16371 {
16372     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16373         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16374
16375     /*
16376      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16377      */
16378     if ( !WhiteOnMove(forwardMostMove) ) {
16379         /* White made time control */
16380         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16381         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16382         /* [HGM] time odds: correct new time quota for time odds! */
16383                                             / WhitePlayer()->timeOdds;
16384         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16385     } else {
16386         lastBlack -= blackTimeRemaining;
16387         /* Black made time control */
16388         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16389                                             / WhitePlayer()->other->timeOdds;
16390         lastWhite = whiteTimeRemaining;
16391     }
16392 }
16393
16394 void
16395 DisplayBothClocks ()
16396 {
16397     int wom = gameMode == EditPosition ?
16398       !blackPlaysFirst : WhiteOnMove(currentMove);
16399     DisplayWhiteClock(whiteTimeRemaining, wom);
16400     DisplayBlackClock(blackTimeRemaining, !wom);
16401 }
16402
16403
16404 /* Timekeeping seems to be a portability nightmare.  I think everyone
16405    has ftime(), but I'm really not sure, so I'm including some ifdefs
16406    to use other calls if you don't.  Clocks will be less accurate if
16407    you have neither ftime nor gettimeofday.
16408 */
16409
16410 /* VS 2008 requires the #include outside of the function */
16411 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16412 #include <sys/timeb.h>
16413 #endif
16414
16415 /* Get the current time as a TimeMark */
16416 void
16417 GetTimeMark (TimeMark *tm)
16418 {
16419 #if HAVE_GETTIMEOFDAY
16420
16421     struct timeval timeVal;
16422     struct timezone timeZone;
16423
16424     gettimeofday(&timeVal, &timeZone);
16425     tm->sec = (long) timeVal.tv_sec;
16426     tm->ms = (int) (timeVal.tv_usec / 1000L);
16427
16428 #else /*!HAVE_GETTIMEOFDAY*/
16429 #if HAVE_FTIME
16430
16431 // include <sys/timeb.h> / moved to just above start of function
16432     struct timeb timeB;
16433
16434     ftime(&timeB);
16435     tm->sec = (long) timeB.time;
16436     tm->ms = (int) timeB.millitm;
16437
16438 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16439     tm->sec = (long) time(NULL);
16440     tm->ms = 0;
16441 #endif
16442 #endif
16443 }
16444
16445 /* Return the difference in milliseconds between two
16446    time marks.  We assume the difference will fit in a long!
16447 */
16448 long
16449 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16450 {
16451     return 1000L*(tm2->sec - tm1->sec) +
16452            (long) (tm2->ms - tm1->ms);
16453 }
16454
16455
16456 /*
16457  * Code to manage the game clocks.
16458  *
16459  * In tournament play, black starts the clock and then white makes a move.
16460  * We give the human user a slight advantage if he is playing white---the
16461  * clocks don't run until he makes his first move, so it takes zero time.
16462  * Also, we don't account for network lag, so we could get out of sync
16463  * with GNU Chess's clock -- but then, referees are always right.
16464  */
16465
16466 static TimeMark tickStartTM;
16467 static long intendedTickLength;
16468
16469 long
16470 NextTickLength (long timeRemaining)
16471 {
16472     long nominalTickLength, nextTickLength;
16473
16474     if (timeRemaining > 0L && timeRemaining <= 10000L)
16475       nominalTickLength = 100L;
16476     else
16477       nominalTickLength = 1000L;
16478     nextTickLength = timeRemaining % nominalTickLength;
16479     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16480
16481     return nextTickLength;
16482 }
16483
16484 /* Adjust clock one minute up or down */
16485 void
16486 AdjustClock (Boolean which, int dir)
16487 {
16488     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16489     if(which) blackTimeRemaining += 60000*dir;
16490     else      whiteTimeRemaining += 60000*dir;
16491     DisplayBothClocks();
16492     adjustedClock = TRUE;
16493 }
16494
16495 /* Stop clocks and reset to a fresh time control */
16496 void
16497 ResetClocks ()
16498 {
16499     (void) StopClockTimer();
16500     if (appData.icsActive) {
16501         whiteTimeRemaining = blackTimeRemaining = 0;
16502     } else if (searchTime) {
16503         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16504         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16505     } else { /* [HGM] correct new time quote for time odds */
16506         whiteTC = blackTC = fullTimeControlString;
16507         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16508         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16509     }
16510     if (whiteFlag || blackFlag) {
16511         DisplayTitle("");
16512         whiteFlag = blackFlag = FALSE;
16513     }
16514     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16515     DisplayBothClocks();
16516     adjustedClock = FALSE;
16517 }
16518
16519 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16520
16521 /* Decrement running clock by amount of time that has passed */
16522 void
16523 DecrementClocks ()
16524 {
16525     long timeRemaining;
16526     long lastTickLength, fudge;
16527     TimeMark now;
16528
16529     if (!appData.clockMode) return;
16530     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16531
16532     GetTimeMark(&now);
16533
16534     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16535
16536     /* Fudge if we woke up a little too soon */
16537     fudge = intendedTickLength - lastTickLength;
16538     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16539
16540     if (WhiteOnMove(forwardMostMove)) {
16541         if(whiteNPS >= 0) lastTickLength = 0;
16542         timeRemaining = whiteTimeRemaining -= lastTickLength;
16543         if(timeRemaining < 0 && !appData.icsActive) {
16544             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16545             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16546                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16547                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16548             }
16549         }
16550         DisplayWhiteClock(whiteTimeRemaining - fudge,
16551                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16552     } else {
16553         if(blackNPS >= 0) lastTickLength = 0;
16554         timeRemaining = blackTimeRemaining -= lastTickLength;
16555         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16556             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16557             if(suddenDeath) {
16558                 blackStartMove = forwardMostMove;
16559                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16560             }
16561         }
16562         DisplayBlackClock(blackTimeRemaining - fudge,
16563                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16564     }
16565     if (CheckFlags()) return;
16566
16567     if(twoBoards) { // count down secondary board's clocks as well
16568         activePartnerTime -= lastTickLength;
16569         partnerUp = 1;
16570         if(activePartner == 'W')
16571             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16572         else
16573             DisplayBlackClock(activePartnerTime, TRUE);
16574         partnerUp = 0;
16575     }
16576
16577     tickStartTM = now;
16578     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16579     StartClockTimer(intendedTickLength);
16580
16581     /* if the time remaining has fallen below the alarm threshold, sound the
16582      * alarm. if the alarm has sounded and (due to a takeback or time control
16583      * with increment) the time remaining has increased to a level above the
16584      * threshold, reset the alarm so it can sound again.
16585      */
16586
16587     if (appData.icsActive && appData.icsAlarm) {
16588
16589         /* make sure we are dealing with the user's clock */
16590         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16591                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16592            )) return;
16593
16594         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16595             alarmSounded = FALSE;
16596         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16597             PlayAlarmSound();
16598             alarmSounded = TRUE;
16599         }
16600     }
16601 }
16602
16603
16604 /* A player has just moved, so stop the previously running
16605    clock and (if in clock mode) start the other one.
16606    We redisplay both clocks in case we're in ICS mode, because
16607    ICS gives us an update to both clocks after every move.
16608    Note that this routine is called *after* forwardMostMove
16609    is updated, so the last fractional tick must be subtracted
16610    from the color that is *not* on move now.
16611 */
16612 void
16613 SwitchClocks (int newMoveNr)
16614 {
16615     long lastTickLength;
16616     TimeMark now;
16617     int flagged = FALSE;
16618
16619     GetTimeMark(&now);
16620
16621     if (StopClockTimer() && appData.clockMode) {
16622         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16623         if (!WhiteOnMove(forwardMostMove)) {
16624             if(blackNPS >= 0) lastTickLength = 0;
16625             blackTimeRemaining -= lastTickLength;
16626            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16627 //         if(pvInfoList[forwardMostMove].time == -1)
16628                  pvInfoList[forwardMostMove].time =               // use GUI time
16629                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16630         } else {
16631            if(whiteNPS >= 0) lastTickLength = 0;
16632            whiteTimeRemaining -= lastTickLength;
16633            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16634 //         if(pvInfoList[forwardMostMove].time == -1)
16635                  pvInfoList[forwardMostMove].time =
16636                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16637         }
16638         flagged = CheckFlags();
16639     }
16640     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16641     CheckTimeControl();
16642
16643     if (flagged || !appData.clockMode) return;
16644
16645     switch (gameMode) {
16646       case MachinePlaysBlack:
16647       case MachinePlaysWhite:
16648       case BeginningOfGame:
16649         if (pausing) return;
16650         break;
16651
16652       case EditGame:
16653       case PlayFromGameFile:
16654       case IcsExamining:
16655         return;
16656
16657       default:
16658         break;
16659     }
16660
16661     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16662         if(WhiteOnMove(forwardMostMove))
16663              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16664         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16665     }
16666
16667     tickStartTM = now;
16668     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16669       whiteTimeRemaining : blackTimeRemaining);
16670     StartClockTimer(intendedTickLength);
16671 }
16672
16673
16674 /* Stop both clocks */
16675 void
16676 StopClocks ()
16677 {
16678     long lastTickLength;
16679     TimeMark now;
16680
16681     if (!StopClockTimer()) return;
16682     if (!appData.clockMode) return;
16683
16684     GetTimeMark(&now);
16685
16686     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16687     if (WhiteOnMove(forwardMostMove)) {
16688         if(whiteNPS >= 0) lastTickLength = 0;
16689         whiteTimeRemaining -= lastTickLength;
16690         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16691     } else {
16692         if(blackNPS >= 0) lastTickLength = 0;
16693         blackTimeRemaining -= lastTickLength;
16694         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16695     }
16696     CheckFlags();
16697 }
16698
16699 /* Start clock of player on move.  Time may have been reset, so
16700    if clock is already running, stop and restart it. */
16701 void
16702 StartClocks ()
16703 {
16704     (void) StopClockTimer(); /* in case it was running already */
16705     DisplayBothClocks();
16706     if (CheckFlags()) return;
16707
16708     if (!appData.clockMode) return;
16709     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16710
16711     GetTimeMark(&tickStartTM);
16712     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16713       whiteTimeRemaining : blackTimeRemaining);
16714
16715    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16716     whiteNPS = blackNPS = -1;
16717     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16718        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16719         whiteNPS = first.nps;
16720     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16721        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16722         blackNPS = first.nps;
16723     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16724         whiteNPS = second.nps;
16725     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16726         blackNPS = second.nps;
16727     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16728
16729     StartClockTimer(intendedTickLength);
16730 }
16731
16732 char *
16733 TimeString (long ms)
16734 {
16735     long second, minute, hour, day;
16736     char *sign = "";
16737     static char buf[32];
16738
16739     if (ms > 0 && ms <= 9900) {
16740       /* convert milliseconds to tenths, rounding up */
16741       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16742
16743       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16744       return buf;
16745     }
16746
16747     /* convert milliseconds to seconds, rounding up */
16748     /* use floating point to avoid strangeness of integer division
16749        with negative dividends on many machines */
16750     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16751
16752     if (second < 0) {
16753         sign = "-";
16754         second = -second;
16755     }
16756
16757     day = second / (60 * 60 * 24);
16758     second = second % (60 * 60 * 24);
16759     hour = second / (60 * 60);
16760     second = second % (60 * 60);
16761     minute = second / 60;
16762     second = second % 60;
16763
16764     if (day > 0)
16765       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16766               sign, day, hour, minute, second);
16767     else if (hour > 0)
16768       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16769     else
16770       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16771
16772     return buf;
16773 }
16774
16775
16776 /*
16777  * This is necessary because some C libraries aren't ANSI C compliant yet.
16778  */
16779 char *
16780 StrStr (char *string, char *match)
16781 {
16782     int i, length;
16783
16784     length = strlen(match);
16785
16786     for (i = strlen(string) - length; i >= 0; i--, string++)
16787       if (!strncmp(match, string, length))
16788         return string;
16789
16790     return NULL;
16791 }
16792
16793 char *
16794 StrCaseStr (char *string, char *match)
16795 {
16796     int i, j, length;
16797
16798     length = strlen(match);
16799
16800     for (i = strlen(string) - length; i >= 0; i--, string++) {
16801         for (j = 0; j < length; j++) {
16802             if (ToLower(match[j]) != ToLower(string[j]))
16803               break;
16804         }
16805         if (j == length) return string;
16806     }
16807
16808     return NULL;
16809 }
16810
16811 #ifndef _amigados
16812 int
16813 StrCaseCmp (char *s1, char *s2)
16814 {
16815     char c1, c2;
16816
16817     for (;;) {
16818         c1 = ToLower(*s1++);
16819         c2 = ToLower(*s2++);
16820         if (c1 > c2) return 1;
16821         if (c1 < c2) return -1;
16822         if (c1 == NULLCHAR) return 0;
16823     }
16824 }
16825
16826
16827 int
16828 ToLower (int c)
16829 {
16830     return isupper(c) ? tolower(c) : c;
16831 }
16832
16833
16834 int
16835 ToUpper (int c)
16836 {
16837     return islower(c) ? toupper(c) : c;
16838 }
16839 #endif /* !_amigados    */
16840
16841 char *
16842 StrSave (char *s)
16843 {
16844   char *ret;
16845
16846   if ((ret = (char *) malloc(strlen(s) + 1)))
16847     {
16848       safeStrCpy(ret, s, strlen(s)+1);
16849     }
16850   return ret;
16851 }
16852
16853 char *
16854 StrSavePtr (char *s, char **savePtr)
16855 {
16856     if (*savePtr) {
16857         free(*savePtr);
16858     }
16859     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16860       safeStrCpy(*savePtr, s, strlen(s)+1);
16861     }
16862     return(*savePtr);
16863 }
16864
16865 char *
16866 PGNDate ()
16867 {
16868     time_t clock;
16869     struct tm *tm;
16870     char buf[MSG_SIZ];
16871
16872     clock = time((time_t *)NULL);
16873     tm = localtime(&clock);
16874     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16875             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16876     return StrSave(buf);
16877 }
16878
16879
16880 char *
16881 PositionToFEN (int move, char *overrideCastling)
16882 {
16883     int i, j, fromX, fromY, toX, toY;
16884     int whiteToPlay;
16885     char buf[MSG_SIZ];
16886     char *p, *q;
16887     int emptycount;
16888     ChessSquare piece;
16889
16890     whiteToPlay = (gameMode == EditPosition) ?
16891       !blackPlaysFirst : (move % 2 == 0);
16892     p = buf;
16893
16894     /* Piece placement data */
16895     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16896         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16897         emptycount = 0;
16898         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16899             if (boards[move][i][j] == EmptySquare) {
16900                 emptycount++;
16901             } else { ChessSquare piece = boards[move][i][j];
16902                 if (emptycount > 0) {
16903                     if(emptycount<10) /* [HGM] can be >= 10 */
16904                         *p++ = '0' + emptycount;
16905                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16906                     emptycount = 0;
16907                 }
16908                 if(PieceToChar(piece) == '+') {
16909                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16910                     *p++ = '+';
16911                     piece = (ChessSquare)(DEMOTED piece);
16912                 }
16913                 *p++ = PieceToChar(piece);
16914                 if(p[-1] == '~') {
16915                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16916                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16917                     *p++ = '~';
16918                 }
16919             }
16920         }
16921         if (emptycount > 0) {
16922             if(emptycount<10) /* [HGM] can be >= 10 */
16923                 *p++ = '0' + emptycount;
16924             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16925             emptycount = 0;
16926         }
16927         *p++ = '/';
16928     }
16929     *(p - 1) = ' ';
16930
16931     /* [HGM] print Crazyhouse or Shogi holdings */
16932     if( gameInfo.holdingsWidth ) {
16933         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16934         q = p;
16935         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16936             piece = boards[move][i][BOARD_WIDTH-1];
16937             if( piece != EmptySquare )
16938               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16939                   *p++ = PieceToChar(piece);
16940         }
16941         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16942             piece = boards[move][BOARD_HEIGHT-i-1][0];
16943             if( piece != EmptySquare )
16944               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16945                   *p++ = PieceToChar(piece);
16946         }
16947
16948         if( q == p ) *p++ = '-';
16949         *p++ = ']';
16950         *p++ = ' ';
16951     }
16952
16953     /* Active color */
16954     *p++ = whiteToPlay ? 'w' : 'b';
16955     *p++ = ' ';
16956
16957   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16958     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16959   } else {
16960   if(nrCastlingRights) {
16961      q = p;
16962      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16963        /* [HGM] write directly from rights */
16964            if(boards[move][CASTLING][2] != NoRights &&
16965               boards[move][CASTLING][0] != NoRights   )
16966                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16967            if(boards[move][CASTLING][2] != NoRights &&
16968               boards[move][CASTLING][1] != NoRights   )
16969                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16970            if(boards[move][CASTLING][5] != NoRights &&
16971               boards[move][CASTLING][3] != NoRights   )
16972                 *p++ = boards[move][CASTLING][3] + AAA;
16973            if(boards[move][CASTLING][5] != NoRights &&
16974               boards[move][CASTLING][4] != NoRights   )
16975                 *p++ = boards[move][CASTLING][4] + AAA;
16976      } else {
16977
16978         /* [HGM] write true castling rights */
16979         if( nrCastlingRights == 6 ) {
16980             int q, k=0;
16981             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16982                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16983             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16984                  boards[move][CASTLING][2] != NoRights  );
16985             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16986                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16987                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16988                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16989                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16990             }
16991             if(q) *p++ = 'Q';
16992             k = 0;
16993             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16994                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16995             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16996                  boards[move][CASTLING][5] != NoRights  );
16997             if(gameInfo.variant == VariantSChess) {
16998                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16999                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17000                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17001                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17002             }
17003             if(q) *p++ = 'q';
17004         }
17005      }
17006      if (q == p) *p++ = '-'; /* No castling rights */
17007      *p++ = ' ';
17008   }
17009
17010   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17011      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17012     /* En passant target square */
17013     if (move > backwardMostMove) {
17014         fromX = moveList[move - 1][0] - AAA;
17015         fromY = moveList[move - 1][1] - ONE;
17016         toX = moveList[move - 1][2] - AAA;
17017         toY = moveList[move - 1][3] - ONE;
17018         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17019             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17020             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17021             fromX == toX) {
17022             /* 2-square pawn move just happened */
17023             *p++ = toX + AAA;
17024             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17025         } else {
17026             *p++ = '-';
17027         }
17028     } else if(move == backwardMostMove) {
17029         // [HGM] perhaps we should always do it like this, and forget the above?
17030         if((signed char)boards[move][EP_STATUS] >= 0) {
17031             *p++ = boards[move][EP_STATUS] + AAA;
17032             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17033         } else {
17034             *p++ = '-';
17035         }
17036     } else {
17037         *p++ = '-';
17038     }
17039     *p++ = ' ';
17040   }
17041   }
17042
17043     /* [HGM] find reversible plies */
17044     {   int i = 0, j=move;
17045
17046         if (appData.debugMode) { int k;
17047             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17048             for(k=backwardMostMove; k<=forwardMostMove; k++)
17049                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17050
17051         }
17052
17053         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17054         if( j == backwardMostMove ) i += initialRulePlies;
17055         sprintf(p, "%d ", i);
17056         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17057     }
17058     /* Fullmove number */
17059     sprintf(p, "%d", (move / 2) + 1);
17060
17061     return StrSave(buf);
17062 }
17063
17064 Boolean
17065 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17066 {
17067     int i, j;
17068     char *p, c;
17069     int emptycount, virgin[BOARD_FILES];
17070     ChessSquare piece;
17071
17072     p = fen;
17073
17074     /* [HGM] by default clear Crazyhouse holdings, if present */
17075     if(gameInfo.holdingsWidth) {
17076        for(i=0; i<BOARD_HEIGHT; i++) {
17077            board[i][0]             = EmptySquare; /* black holdings */
17078            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17079            board[i][1]             = (ChessSquare) 0; /* black counts */
17080            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17081        }
17082     }
17083
17084     /* Piece placement data */
17085     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17086         j = 0;
17087         for (;;) {
17088             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17089                 if (*p == '/') p++;
17090                 emptycount = gameInfo.boardWidth - j;
17091                 while (emptycount--)
17092                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17093                 break;
17094 #if(BOARD_FILES >= 10)
17095             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17096                 p++; emptycount=10;
17097                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17098                 while (emptycount--)
17099                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17100 #endif
17101             } else if (isdigit(*p)) {
17102                 emptycount = *p++ - '0';
17103                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17104                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17105                 while (emptycount--)
17106                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17107             } else if (*p == '+' || isalpha(*p)) {
17108                 if (j >= gameInfo.boardWidth) return FALSE;
17109                 if(*p=='+') {
17110                     piece = CharToPiece(*++p);
17111                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17112                     piece = (ChessSquare) (PROMOTED piece ); p++;
17113                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17114                 } else piece = CharToPiece(*p++);
17115
17116                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17117                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17118                     piece = (ChessSquare) (PROMOTED piece);
17119                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17120                     p++;
17121                 }
17122                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17123             } else {
17124                 return FALSE;
17125             }
17126         }
17127     }
17128     while (*p == '/' || *p == ' ') p++;
17129
17130     /* [HGM] look for Crazyhouse holdings here */
17131     while(*p==' ') p++;
17132     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17133         if(*p == '[') p++;
17134         if(*p == '-' ) p++; /* empty holdings */ else {
17135             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17136             /* if we would allow FEN reading to set board size, we would   */
17137             /* have to add holdings and shift the board read so far here   */
17138             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17139                 p++;
17140                 if((int) piece >= (int) BlackPawn ) {
17141                     i = (int)piece - (int)BlackPawn;
17142                     i = PieceToNumber((ChessSquare)i);
17143                     if( i >= gameInfo.holdingsSize ) return FALSE;
17144                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17145                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17146                 } else {
17147                     i = (int)piece - (int)WhitePawn;
17148                     i = PieceToNumber((ChessSquare)i);
17149                     if( i >= gameInfo.holdingsSize ) return FALSE;
17150                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17151                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17152                 }
17153             }
17154         }
17155         if(*p == ']') p++;
17156     }
17157
17158     while(*p == ' ') p++;
17159
17160     /* Active color */
17161     c = *p++;
17162     if(appData.colorNickNames) {
17163       if( c == appData.colorNickNames[0] ) c = 'w'; else
17164       if( c == appData.colorNickNames[1] ) c = 'b';
17165     }
17166     switch (c) {
17167       case 'w':
17168         *blackPlaysFirst = FALSE;
17169         break;
17170       case 'b':
17171         *blackPlaysFirst = TRUE;
17172         break;
17173       default:
17174         return FALSE;
17175     }
17176
17177     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17178     /* return the extra info in global variiables             */
17179
17180     /* set defaults in case FEN is incomplete */
17181     board[EP_STATUS] = EP_UNKNOWN;
17182     for(i=0; i<nrCastlingRights; i++ ) {
17183         board[CASTLING][i] =
17184             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17185     }   /* assume possible unless obviously impossible */
17186     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17187     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17188     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17189                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17190     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17191     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17192     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17193                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17194     FENrulePlies = 0;
17195
17196     while(*p==' ') p++;
17197     if(nrCastlingRights) {
17198       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17199       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17200           /* castling indicator present, so default becomes no castlings */
17201           for(i=0; i<nrCastlingRights; i++ ) {
17202                  board[CASTLING][i] = NoRights;
17203           }
17204       }
17205       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17206              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17207              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17208              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17209         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17210
17211         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17212             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17213             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17214         }
17215         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17216             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17217         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17218                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17219         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17220                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17221         switch(c) {
17222           case'K':
17223               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17224               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17225               board[CASTLING][2] = whiteKingFile;
17226               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17227               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17228               break;
17229           case'Q':
17230               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17231               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17232               board[CASTLING][2] = whiteKingFile;
17233               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17234               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17235               break;
17236           case'k':
17237               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17238               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17239               board[CASTLING][5] = blackKingFile;
17240               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17241               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17242               break;
17243           case'q':
17244               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17245               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17246               board[CASTLING][5] = blackKingFile;
17247               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17248               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17249           case '-':
17250               break;
17251           default: /* FRC castlings */
17252               if(c >= 'a') { /* black rights */
17253                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17254                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17255                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17256                   if(i == BOARD_RGHT) break;
17257                   board[CASTLING][5] = i;
17258                   c -= AAA;
17259                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17260                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17261                   if(c > i)
17262                       board[CASTLING][3] = c;
17263                   else
17264                       board[CASTLING][4] = c;
17265               } else { /* white rights */
17266                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17267                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17268                     if(board[0][i] == WhiteKing) break;
17269                   if(i == BOARD_RGHT) break;
17270                   board[CASTLING][2] = i;
17271                   c -= AAA - 'a' + 'A';
17272                   if(board[0][c] >= WhiteKing) break;
17273                   if(c > i)
17274                       board[CASTLING][0] = c;
17275                   else
17276                       board[CASTLING][1] = c;
17277               }
17278         }
17279       }
17280       for(i=0; i<nrCastlingRights; i++)
17281         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17282       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17283     if (appData.debugMode) {
17284         fprintf(debugFP, "FEN castling rights:");
17285         for(i=0; i<nrCastlingRights; i++)
17286         fprintf(debugFP, " %d", board[CASTLING][i]);
17287         fprintf(debugFP, "\n");
17288     }
17289
17290       while(*p==' ') p++;
17291     }
17292
17293     /* read e.p. field in games that know e.p. capture */
17294     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17295        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17296       if(*p=='-') {
17297         p++; board[EP_STATUS] = EP_NONE;
17298       } else {
17299          char c = *p++ - AAA;
17300
17301          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17302          if(*p >= '0' && *p <='9') p++;
17303          board[EP_STATUS] = c;
17304       }
17305     }
17306
17307
17308     if(sscanf(p, "%d", &i) == 1) {
17309         FENrulePlies = i; /* 50-move ply counter */
17310         /* (The move number is still ignored)    */
17311     }
17312
17313     return TRUE;
17314 }
17315
17316 void
17317 EditPositionPasteFEN (char *fen)
17318 {
17319   if (fen != NULL) {
17320     Board initial_position;
17321
17322     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17323       DisplayError(_("Bad FEN position in clipboard"), 0);
17324       return ;
17325     } else {
17326       int savedBlackPlaysFirst = blackPlaysFirst;
17327       EditPositionEvent();
17328       blackPlaysFirst = savedBlackPlaysFirst;
17329       CopyBoard(boards[0], initial_position);
17330       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17331       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17332       DisplayBothClocks();
17333       DrawPosition(FALSE, boards[currentMove]);
17334     }
17335   }
17336 }
17337
17338 static char cseq[12] = "\\   ";
17339
17340 Boolean
17341 set_cont_sequence (char *new_seq)
17342 {
17343     int len;
17344     Boolean ret;
17345
17346     // handle bad attempts to set the sequence
17347         if (!new_seq)
17348                 return 0; // acceptable error - no debug
17349
17350     len = strlen(new_seq);
17351     ret = (len > 0) && (len < sizeof(cseq));
17352     if (ret)
17353       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17354     else if (appData.debugMode)
17355       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17356     return ret;
17357 }
17358
17359 /*
17360     reformat a source message so words don't cross the width boundary.  internal
17361     newlines are not removed.  returns the wrapped size (no null character unless
17362     included in source message).  If dest is NULL, only calculate the size required
17363     for the dest buffer.  lp argument indicats line position upon entry, and it's
17364     passed back upon exit.
17365 */
17366 int
17367 wrap (char *dest, char *src, int count, int width, int *lp)
17368 {
17369     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17370
17371     cseq_len = strlen(cseq);
17372     old_line = line = *lp;
17373     ansi = len = clen = 0;
17374
17375     for (i=0; i < count; i++)
17376     {
17377         if (src[i] == '\033')
17378             ansi = 1;
17379
17380         // if we hit the width, back up
17381         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17382         {
17383             // store i & len in case the word is too long
17384             old_i = i, old_len = len;
17385
17386             // find the end of the last word
17387             while (i && src[i] != ' ' && src[i] != '\n')
17388             {
17389                 i--;
17390                 len--;
17391             }
17392
17393             // word too long?  restore i & len before splitting it
17394             if ((old_i-i+clen) >= width)
17395             {
17396                 i = old_i;
17397                 len = old_len;
17398             }
17399
17400             // extra space?
17401             if (i && src[i-1] == ' ')
17402                 len--;
17403
17404             if (src[i] != ' ' && src[i] != '\n')
17405             {
17406                 i--;
17407                 if (len)
17408                     len--;
17409             }
17410
17411             // now append the newline and continuation sequence
17412             if (dest)
17413                 dest[len] = '\n';
17414             len++;
17415             if (dest)
17416                 strncpy(dest+len, cseq, cseq_len);
17417             len += cseq_len;
17418             line = cseq_len;
17419             clen = cseq_len;
17420             continue;
17421         }
17422
17423         if (dest)
17424             dest[len] = src[i];
17425         len++;
17426         if (!ansi)
17427             line++;
17428         if (src[i] == '\n')
17429             line = 0;
17430         if (src[i] == 'm')
17431             ansi = 0;
17432     }
17433     if (dest && appData.debugMode)
17434     {
17435         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17436             count, width, line, len, *lp);
17437         show_bytes(debugFP, src, count);
17438         fprintf(debugFP, "\ndest: ");
17439         show_bytes(debugFP, dest, len);
17440         fprintf(debugFP, "\n");
17441     }
17442     *lp = dest ? line : old_line;
17443
17444     return len;
17445 }
17446
17447 // [HGM] vari: routines for shelving variations
17448 Boolean modeRestore = FALSE;
17449
17450 void
17451 PushInner (int firstMove, int lastMove)
17452 {
17453         int i, j, nrMoves = lastMove - firstMove;
17454
17455         // push current tail of game on stack
17456         savedResult[storedGames] = gameInfo.result;
17457         savedDetails[storedGames] = gameInfo.resultDetails;
17458         gameInfo.resultDetails = NULL;
17459         savedFirst[storedGames] = firstMove;
17460         savedLast [storedGames] = lastMove;
17461         savedFramePtr[storedGames] = framePtr;
17462         framePtr -= nrMoves; // reserve space for the boards
17463         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17464             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17465             for(j=0; j<MOVE_LEN; j++)
17466                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17467             for(j=0; j<2*MOVE_LEN; j++)
17468                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17469             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17470             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17471             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17472             pvInfoList[firstMove+i-1].depth = 0;
17473             commentList[framePtr+i] = commentList[firstMove+i];
17474             commentList[firstMove+i] = NULL;
17475         }
17476
17477         storedGames++;
17478         forwardMostMove = firstMove; // truncate game so we can start variation
17479 }
17480
17481 void
17482 PushTail (int firstMove, int lastMove)
17483 {
17484         if(appData.icsActive) { // only in local mode
17485                 forwardMostMove = currentMove; // mimic old ICS behavior
17486                 return;
17487         }
17488         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17489
17490         PushInner(firstMove, lastMove);
17491         if(storedGames == 1) GreyRevert(FALSE);
17492         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17493 }
17494
17495 void
17496 PopInner (Boolean annotate)
17497 {
17498         int i, j, nrMoves;
17499         char buf[8000], moveBuf[20];
17500
17501         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17502         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17503         nrMoves = savedLast[storedGames] - currentMove;
17504         if(annotate) {
17505                 int cnt = 10;
17506                 if(!WhiteOnMove(currentMove))
17507                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17508                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17509                 for(i=currentMove; i<forwardMostMove; i++) {
17510                         if(WhiteOnMove(i))
17511                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17512                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17513                         strcat(buf, moveBuf);
17514                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17515                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17516                 }
17517                 strcat(buf, ")");
17518         }
17519         for(i=1; i<=nrMoves; i++) { // copy last variation back
17520             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17521             for(j=0; j<MOVE_LEN; j++)
17522                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17523             for(j=0; j<2*MOVE_LEN; j++)
17524                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17525             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17526             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17527             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17528             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17529             commentList[currentMove+i] = commentList[framePtr+i];
17530             commentList[framePtr+i] = NULL;
17531         }
17532         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17533         framePtr = savedFramePtr[storedGames];
17534         gameInfo.result = savedResult[storedGames];
17535         if(gameInfo.resultDetails != NULL) {
17536             free(gameInfo.resultDetails);
17537       }
17538         gameInfo.resultDetails = savedDetails[storedGames];
17539         forwardMostMove = currentMove + nrMoves;
17540 }
17541
17542 Boolean
17543 PopTail (Boolean annotate)
17544 {
17545         if(appData.icsActive) return FALSE; // only in local mode
17546         if(!storedGames) return FALSE; // sanity
17547         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17548
17549         PopInner(annotate);
17550         if(currentMove < forwardMostMove) ForwardEvent(); else
17551         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17552
17553         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17554         return TRUE;
17555 }
17556
17557 void
17558 CleanupTail ()
17559 {       // remove all shelved variations
17560         int i;
17561         for(i=0; i<storedGames; i++) {
17562             if(savedDetails[i])
17563                 free(savedDetails[i]);
17564             savedDetails[i] = NULL;
17565         }
17566         for(i=framePtr; i<MAX_MOVES; i++) {
17567                 if(commentList[i]) free(commentList[i]);
17568                 commentList[i] = NULL;
17569         }
17570         framePtr = MAX_MOVES-1;
17571         storedGames = 0;
17572 }
17573
17574 void
17575 LoadVariation (int index, char *text)
17576 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17577         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17578         int level = 0, move;
17579
17580         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17581         // first find outermost bracketing variation
17582         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17583             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17584                 if(*p == '{') wait = '}'; else
17585                 if(*p == '[') wait = ']'; else
17586                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17587                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17588             }
17589             if(*p == wait) wait = NULLCHAR; // closing ]} found
17590             p++;
17591         }
17592         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17593         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17594         end[1] = NULLCHAR; // clip off comment beyond variation
17595         ToNrEvent(currentMove-1);
17596         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17597         // kludge: use ParsePV() to append variation to game
17598         move = currentMove;
17599         ParsePV(start, TRUE, TRUE);
17600         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17601         ClearPremoveHighlights();
17602         CommentPopDown();
17603         ToNrEvent(currentMove+1);
17604 }
17605
17606 void
17607 LoadTheme ()
17608 {
17609     char *p, *q, buf[MSG_SIZ];
17610     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17611         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17612         ParseArgsFromString(buf);
17613         ActivateTheme(TRUE); // also redo colors
17614         return;
17615     }
17616     p = nickName;
17617     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17618     {
17619         int len;
17620         q = appData.themeNames;
17621         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17622       if(appData.useBitmaps) {
17623         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17624                 appData.liteBackTextureFile, appData.darkBackTextureFile, 
17625                 appData.liteBackTextureMode,
17626                 appData.darkBackTextureMode );
17627       } else {
17628         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17629                 Col2Text(2),   // lightSquareColor
17630                 Col2Text(3) ); // darkSquareColor
17631       }
17632       if(appData.useBorder) {
17633         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17634                 appData.border);
17635       } else {
17636         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17637       }
17638       if(appData.useFont) {
17639         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17640                 appData.renderPiecesWithFont,
17641                 appData.fontToPieceTable,
17642                 Col2Text(9),    // appData.fontBackColorWhite
17643                 Col2Text(10) ); // appData.fontForeColorBlack
17644       } else {
17645         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17646                 appData.pieceDirectory);
17647         if(!appData.pieceDirectory[0])
17648           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17649                 Col2Text(0),   // whitePieceColor
17650                 Col2Text(1) ); // blackPieceColor
17651       }
17652       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17653                 Col2Text(4),   // highlightSquareColor
17654                 Col2Text(5) ); // premoveHighlightColor
17655         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17656         if(insert != q) insert[-1] = NULLCHAR;
17657         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17658         if(q)   free(q);
17659     }
17660     ActivateTheme(FALSE);
17661 }