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