Implement auto-creation of ICS logon file
[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                 }
10651             }
10652
10653             /* Tell program how game ended in case it is learning */
10654             /* [HGM] Moved this to after saving the PGN, just in case */
10655             /* engine died and we got here through time loss. In that */
10656             /* case we will get a fatal error writing the pipe, which */
10657             /* would otherwise lose us the PGN.                       */
10658             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10659             /* output during GameEnds should never be fatal anymore   */
10660             if (gameMode == MachinePlaysWhite ||
10661                 gameMode == MachinePlaysBlack ||
10662                 gameMode == TwoMachinesPlay ||
10663                 gameMode == IcsPlayingWhite ||
10664                 gameMode == IcsPlayingBlack ||
10665                 gameMode == BeginningOfGame) {
10666                 char buf[MSG_SIZ];
10667                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10668                         resultDetails);
10669                 if (first.pr != NoProc) {
10670                     SendToProgram(buf, &first);
10671                 }
10672                 if (second.pr != NoProc &&
10673                     gameMode == TwoMachinesPlay) {
10674                     SendToProgram(buf, &second);
10675                 }
10676             }
10677         }
10678
10679         if (appData.icsActive) {
10680             if (appData.quietPlay &&
10681                 (gameMode == IcsPlayingWhite ||
10682                  gameMode == IcsPlayingBlack)) {
10683                 SendToICS(ics_prefix);
10684                 SendToICS("set shout 1\n");
10685             }
10686             nextGameMode = IcsIdle;
10687             ics_user_moved = FALSE;
10688             /* clean up premove.  It's ugly when the game has ended and the
10689              * premove highlights are still on the board.
10690              */
10691             if (gotPremove) {
10692               gotPremove = FALSE;
10693               ClearPremoveHighlights();
10694               DrawPosition(FALSE, boards[currentMove]);
10695             }
10696             if (whosays == GE_ICS) {
10697                 switch (result) {
10698                 case WhiteWins:
10699                     if (gameMode == IcsPlayingWhite)
10700                         PlayIcsWinSound();
10701                     else if(gameMode == IcsPlayingBlack)
10702                         PlayIcsLossSound();
10703                     break;
10704                 case BlackWins:
10705                     if (gameMode == IcsPlayingBlack)
10706                         PlayIcsWinSound();
10707                     else if(gameMode == IcsPlayingWhite)
10708                         PlayIcsLossSound();
10709                     break;
10710                 case GameIsDrawn:
10711                     PlayIcsDrawSound();
10712                     break;
10713                 default:
10714                     PlayIcsUnfinishedSound();
10715                 }
10716             }
10717         } else if (gameMode == EditGame ||
10718                    gameMode == PlayFromGameFile ||
10719                    gameMode == AnalyzeMode ||
10720                    gameMode == AnalyzeFile) {
10721             nextGameMode = gameMode;
10722         } else {
10723             nextGameMode = EndOfGame;
10724         }
10725         pausing = FALSE;
10726         ModeHighlight();
10727     } else {
10728         nextGameMode = gameMode;
10729     }
10730
10731     if (appData.noChessProgram) {
10732         gameMode = nextGameMode;
10733         ModeHighlight();
10734         endingGame = 0; /* [HGM] crash */
10735         return;
10736     }
10737
10738     if (first.reuse) {
10739         /* Put first chess program into idle state */
10740         if (first.pr != NoProc &&
10741             (gameMode == MachinePlaysWhite ||
10742              gameMode == MachinePlaysBlack ||
10743              gameMode == TwoMachinesPlay ||
10744              gameMode == IcsPlayingWhite ||
10745              gameMode == IcsPlayingBlack ||
10746              gameMode == BeginningOfGame)) {
10747             SendToProgram("force\n", &first);
10748             if (first.usePing) {
10749               char buf[MSG_SIZ];
10750               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10751               SendToProgram(buf, &first);
10752             }
10753         }
10754     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10755         /* Kill off first chess program */
10756         if (first.isr != NULL)
10757           RemoveInputSource(first.isr);
10758         first.isr = NULL;
10759
10760         if (first.pr != NoProc) {
10761             ExitAnalyzeMode();
10762             DoSleep( appData.delayBeforeQuit );
10763             SendToProgram("quit\n", &first);
10764             DoSleep( appData.delayAfterQuit );
10765             DestroyChildProcess(first.pr, first.useSigterm);
10766         }
10767         first.pr = NoProc;
10768     }
10769     if (second.reuse) {
10770         /* Put second chess program into idle state */
10771         if (second.pr != NoProc &&
10772             gameMode == TwoMachinesPlay) {
10773             SendToProgram("force\n", &second);
10774             if (second.usePing) {
10775               char buf[MSG_SIZ];
10776               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10777               SendToProgram(buf, &second);
10778             }
10779         }
10780     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10781         /* Kill off second chess program */
10782         if (second.isr != NULL)
10783           RemoveInputSource(second.isr);
10784         second.isr = NULL;
10785
10786         if (second.pr != NoProc) {
10787             DoSleep( appData.delayBeforeQuit );
10788             SendToProgram("quit\n", &second);
10789             DoSleep( appData.delayAfterQuit );
10790             DestroyChildProcess(second.pr, second.useSigterm);
10791         }
10792         second.pr = NoProc;
10793     }
10794
10795     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10796         char resChar = '=';
10797         switch (result) {
10798         case WhiteWins:
10799           resChar = '+';
10800           if (first.twoMachinesColor[0] == 'w') {
10801             first.matchWins++;
10802           } else {
10803             second.matchWins++;
10804           }
10805           break;
10806         case BlackWins:
10807           resChar = '-';
10808           if (first.twoMachinesColor[0] == 'b') {
10809             first.matchWins++;
10810           } else {
10811             second.matchWins++;
10812           }
10813           break;
10814         case GameUnfinished:
10815           resChar = ' ';
10816         default:
10817           break;
10818         }
10819
10820         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10821         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10822             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10823             ReserveGame(nextGame, resChar); // sets nextGame
10824             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10825             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10826         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10827
10828         if (nextGame <= appData.matchGames && !abortMatch) {
10829             gameMode = nextGameMode;
10830             matchGame = nextGame; // this will be overruled in tourney mode!
10831             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10832             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10833             endingGame = 0; /* [HGM] crash */
10834             return;
10835         } else {
10836             gameMode = nextGameMode;
10837             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10838                      first.tidy, second.tidy,
10839                      first.matchWins, second.matchWins,
10840                      appData.matchGames - (first.matchWins + second.matchWins));
10841             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10842             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10843             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10844             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10845                 first.twoMachinesColor = "black\n";
10846                 second.twoMachinesColor = "white\n";
10847             } else {
10848                 first.twoMachinesColor = "white\n";
10849                 second.twoMachinesColor = "black\n";
10850             }
10851         }
10852     }
10853     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10854         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10855       ExitAnalyzeMode();
10856     gameMode = nextGameMode;
10857     ModeHighlight();
10858     endingGame = 0;  /* [HGM] crash */
10859     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10860         if(matchMode == TRUE) { // match through command line: exit with or without popup
10861             if(ranking) {
10862                 ToNrEvent(forwardMostMove);
10863                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10864                 else ExitEvent(0);
10865             } else DisplayFatalError(buf, 0, 0);
10866         } else { // match through menu; just stop, with or without popup
10867             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10868             ModeHighlight();
10869             if(ranking){
10870                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10871             } else DisplayNote(buf);
10872       }
10873       if(ranking) free(ranking);
10874     }
10875 }
10876
10877 /* Assumes program was just initialized (initString sent).
10878    Leaves program in force mode. */
10879 void
10880 FeedMovesToProgram (ChessProgramState *cps, int upto)
10881 {
10882     int i;
10883
10884     if (appData.debugMode)
10885       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10886               startedFromSetupPosition ? "position and " : "",
10887               backwardMostMove, upto, cps->which);
10888     if(currentlyInitializedVariant != gameInfo.variant) {
10889       char buf[MSG_SIZ];
10890         // [HGM] variantswitch: make engine aware of new variant
10891         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10892                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10893         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10894         SendToProgram(buf, cps);
10895         currentlyInitializedVariant = gameInfo.variant;
10896     }
10897     SendToProgram("force\n", cps);
10898     if (startedFromSetupPosition) {
10899         SendBoard(cps, backwardMostMove);
10900     if (appData.debugMode) {
10901         fprintf(debugFP, "feedMoves\n");
10902     }
10903     }
10904     for (i = backwardMostMove; i < upto; i++) {
10905         SendMoveToProgram(i, cps);
10906     }
10907 }
10908
10909
10910 int
10911 ResurrectChessProgram ()
10912 {
10913      /* The chess program may have exited.
10914         If so, restart it and feed it all the moves made so far. */
10915     static int doInit = 0;
10916
10917     if (appData.noChessProgram) return 1;
10918
10919     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10920         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10921         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10922         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10923     } else {
10924         if (first.pr != NoProc) return 1;
10925         StartChessProgram(&first);
10926     }
10927     InitChessProgram(&first, FALSE);
10928     FeedMovesToProgram(&first, currentMove);
10929
10930     if (!first.sendTime) {
10931         /* can't tell gnuchess what its clock should read,
10932            so we bow to its notion. */
10933         ResetClocks();
10934         timeRemaining[0][currentMove] = whiteTimeRemaining;
10935         timeRemaining[1][currentMove] = blackTimeRemaining;
10936     }
10937
10938     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10939                 appData.icsEngineAnalyze) && first.analysisSupport) {
10940       SendToProgram("analyze\n", &first);
10941       first.analyzing = TRUE;
10942     }
10943     return 1;
10944 }
10945
10946 /*
10947  * Button procedures
10948  */
10949 void
10950 Reset (int redraw, int init)
10951 {
10952     int i;
10953
10954     if (appData.debugMode) {
10955         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10956                 redraw, init, gameMode);
10957     }
10958     CleanupTail(); // [HGM] vari: delete any stored variations
10959     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10960     pausing = pauseExamInvalid = FALSE;
10961     startedFromSetupPosition = blackPlaysFirst = FALSE;
10962     firstMove = TRUE;
10963     whiteFlag = blackFlag = FALSE;
10964     userOfferedDraw = FALSE;
10965     hintRequested = bookRequested = FALSE;
10966     first.maybeThinking = FALSE;
10967     second.maybeThinking = FALSE;
10968     first.bookSuspend = FALSE; // [HGM] book
10969     second.bookSuspend = FALSE;
10970     thinkOutput[0] = NULLCHAR;
10971     lastHint[0] = NULLCHAR;
10972     ClearGameInfo(&gameInfo);
10973     gameInfo.variant = StringToVariant(appData.variant);
10974     ics_user_moved = ics_clock_paused = FALSE;
10975     ics_getting_history = H_FALSE;
10976     ics_gamenum = -1;
10977     white_holding[0] = black_holding[0] = NULLCHAR;
10978     ClearProgramStats();
10979     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10980
10981     ResetFrontEnd();
10982     ClearHighlights();
10983     flipView = appData.flipView;
10984     ClearPremoveHighlights();
10985     gotPremove = FALSE;
10986     alarmSounded = FALSE;
10987
10988     GameEnds(EndOfFile, NULL, GE_PLAYER);
10989     if(appData.serverMovesName != NULL) {
10990         /* [HGM] prepare to make moves file for broadcasting */
10991         clock_t t = clock();
10992         if(serverMoves != NULL) fclose(serverMoves);
10993         serverMoves = fopen(appData.serverMovesName, "r");
10994         if(serverMoves != NULL) {
10995             fclose(serverMoves);
10996             /* delay 15 sec before overwriting, so all clients can see end */
10997             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10998         }
10999         serverMoves = fopen(appData.serverMovesName, "w");
11000     }
11001
11002     ExitAnalyzeMode();
11003     gameMode = BeginningOfGame;
11004     ModeHighlight();
11005     if(appData.icsActive) gameInfo.variant = VariantNormal;
11006     currentMove = forwardMostMove = backwardMostMove = 0;
11007     MarkTargetSquares(1);
11008     InitPosition(redraw);
11009     for (i = 0; i < MAX_MOVES; i++) {
11010         if (commentList[i] != NULL) {
11011             free(commentList[i]);
11012             commentList[i] = NULL;
11013         }
11014     }
11015     ResetClocks();
11016     timeRemaining[0][0] = whiteTimeRemaining;
11017     timeRemaining[1][0] = blackTimeRemaining;
11018
11019     if (first.pr == NoProc) {
11020         StartChessProgram(&first);
11021     }
11022     if (init) {
11023             InitChessProgram(&first, startedFromSetupPosition);
11024     }
11025     DisplayTitle("");
11026     DisplayMessage("", "");
11027     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11028     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11029     ClearMap();        // [HGM] exclude: invalidate map
11030 }
11031
11032 void
11033 AutoPlayGameLoop ()
11034 {
11035     for (;;) {
11036         if (!AutoPlayOneMove())
11037           return;
11038         if (matchMode || appData.timeDelay == 0)
11039           continue;
11040         if (appData.timeDelay < 0)
11041           return;
11042         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11043         break;
11044     }
11045 }
11046
11047 void
11048 AnalyzeNextGame()
11049 {
11050     ReloadGame(1); // next game
11051 }
11052
11053 int
11054 AutoPlayOneMove ()
11055 {
11056     int fromX, fromY, toX, toY;
11057
11058     if (appData.debugMode) {
11059       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11060     }
11061
11062     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11063       return FALSE;
11064
11065     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11066       pvInfoList[currentMove].depth = programStats.depth;
11067       pvInfoList[currentMove].score = programStats.score;
11068       pvInfoList[currentMove].time  = 0;
11069       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11070     }
11071
11072     if (currentMove >= forwardMostMove) {
11073       if(gameMode == AnalyzeFile) {
11074           if(appData.loadGameIndex == -1) {
11075             GameEnds(EndOfFile, NULL, GE_FILE);
11076           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11077           } else {
11078           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11079         }
11080       }
11081 //      gameMode = EndOfGame;
11082 //      ModeHighlight();
11083
11084       /* [AS] Clear current move marker at the end of a game */
11085       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11086
11087       return FALSE;
11088     }
11089
11090     toX = moveList[currentMove][2] - AAA;
11091     toY = moveList[currentMove][3] - ONE;
11092
11093     if (moveList[currentMove][1] == '@') {
11094         if (appData.highlightLastMove) {
11095             SetHighlights(-1, -1, toX, toY);
11096         }
11097     } else {
11098         fromX = moveList[currentMove][0] - AAA;
11099         fromY = moveList[currentMove][1] - ONE;
11100
11101         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11102
11103         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11104
11105         if (appData.highlightLastMove) {
11106             SetHighlights(fromX, fromY, toX, toY);
11107         }
11108     }
11109     DisplayMove(currentMove);
11110     SendMoveToProgram(currentMove++, &first);
11111     DisplayBothClocks();
11112     DrawPosition(FALSE, boards[currentMove]);
11113     // [HGM] PV info: always display, routine tests if empty
11114     DisplayComment(currentMove - 1, commentList[currentMove]);
11115     return TRUE;
11116 }
11117
11118
11119 int
11120 LoadGameOneMove (ChessMove readAhead)
11121 {
11122     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11123     char promoChar = NULLCHAR;
11124     ChessMove moveType;
11125     char move[MSG_SIZ];
11126     char *p, *q;
11127
11128     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11129         gameMode != AnalyzeMode && gameMode != Training) {
11130         gameFileFP = NULL;
11131         return FALSE;
11132     }
11133
11134     yyboardindex = forwardMostMove;
11135     if (readAhead != EndOfFile) {
11136       moveType = readAhead;
11137     } else {
11138       if (gameFileFP == NULL)
11139           return FALSE;
11140       moveType = (ChessMove) Myylex();
11141     }
11142
11143     done = FALSE;
11144     switch (moveType) {
11145       case Comment:
11146         if (appData.debugMode)
11147           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11148         p = yy_text;
11149
11150         /* append the comment but don't display it */
11151         AppendComment(currentMove, p, FALSE);
11152         return TRUE;
11153
11154       case WhiteCapturesEnPassant:
11155       case BlackCapturesEnPassant:
11156       case WhitePromotion:
11157       case BlackPromotion:
11158       case WhiteNonPromotion:
11159       case BlackNonPromotion:
11160       case NormalMove:
11161       case WhiteKingSideCastle:
11162       case WhiteQueenSideCastle:
11163       case BlackKingSideCastle:
11164       case BlackQueenSideCastle:
11165       case WhiteKingSideCastleWild:
11166       case WhiteQueenSideCastleWild:
11167       case BlackKingSideCastleWild:
11168       case BlackQueenSideCastleWild:
11169       /* PUSH Fabien */
11170       case WhiteHSideCastleFR:
11171       case WhiteASideCastleFR:
11172       case BlackHSideCastleFR:
11173       case BlackASideCastleFR:
11174       /* POP Fabien */
11175         if (appData.debugMode)
11176           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11177         fromX = currentMoveString[0] - AAA;
11178         fromY = currentMoveString[1] - ONE;
11179         toX = currentMoveString[2] - AAA;
11180         toY = currentMoveString[3] - ONE;
11181         promoChar = currentMoveString[4];
11182         break;
11183
11184       case WhiteDrop:
11185       case BlackDrop:
11186         if (appData.debugMode)
11187           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11188         fromX = moveType == WhiteDrop ?
11189           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11190         (int) CharToPiece(ToLower(currentMoveString[0]));
11191         fromY = DROP_RANK;
11192         toX = currentMoveString[2] - AAA;
11193         toY = currentMoveString[3] - ONE;
11194         break;
11195
11196       case WhiteWins:
11197       case BlackWins:
11198       case GameIsDrawn:
11199       case GameUnfinished:
11200         if (appData.debugMode)
11201           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11202         p = strchr(yy_text, '{');
11203         if (p == NULL) p = strchr(yy_text, '(');
11204         if (p == NULL) {
11205             p = yy_text;
11206             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11207         } else {
11208             q = strchr(p, *p == '{' ? '}' : ')');
11209             if (q != NULL) *q = NULLCHAR;
11210             p++;
11211         }
11212         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11213         GameEnds(moveType, p, GE_FILE);
11214         done = TRUE;
11215         if (cmailMsgLoaded) {
11216             ClearHighlights();
11217             flipView = WhiteOnMove(currentMove);
11218             if (moveType == GameUnfinished) flipView = !flipView;
11219             if (appData.debugMode)
11220               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11221         }
11222         break;
11223
11224       case EndOfFile:
11225         if (appData.debugMode)
11226           fprintf(debugFP, "Parser hit end of file\n");
11227         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11228           case MT_NONE:
11229           case MT_CHECK:
11230             break;
11231           case MT_CHECKMATE:
11232           case MT_STAINMATE:
11233             if (WhiteOnMove(currentMove)) {
11234                 GameEnds(BlackWins, "Black mates", GE_FILE);
11235             } else {
11236                 GameEnds(WhiteWins, "White mates", GE_FILE);
11237             }
11238             break;
11239           case MT_STALEMATE:
11240             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11241             break;
11242         }
11243         done = TRUE;
11244         break;
11245
11246       case MoveNumberOne:
11247         if (lastLoadGameStart == GNUChessGame) {
11248             /* GNUChessGames have numbers, but they aren't move numbers */
11249             if (appData.debugMode)
11250               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11251                       yy_text, (int) moveType);
11252             return LoadGameOneMove(EndOfFile); /* tail recursion */
11253         }
11254         /* else fall thru */
11255
11256       case XBoardGame:
11257       case GNUChessGame:
11258       case PGNTag:
11259         /* Reached start of next game in file */
11260         if (appData.debugMode)
11261           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11262         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11263           case MT_NONE:
11264           case MT_CHECK:
11265             break;
11266           case MT_CHECKMATE:
11267           case MT_STAINMATE:
11268             if (WhiteOnMove(currentMove)) {
11269                 GameEnds(BlackWins, "Black mates", GE_FILE);
11270             } else {
11271                 GameEnds(WhiteWins, "White mates", GE_FILE);
11272             }
11273             break;
11274           case MT_STALEMATE:
11275             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11276             break;
11277         }
11278         done = TRUE;
11279         break;
11280
11281       case PositionDiagram:     /* should not happen; ignore */
11282       case ElapsedTime:         /* ignore */
11283       case NAG:                 /* ignore */
11284         if (appData.debugMode)
11285           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11286                   yy_text, (int) moveType);
11287         return LoadGameOneMove(EndOfFile); /* tail recursion */
11288
11289       case IllegalMove:
11290         if (appData.testLegality) {
11291             if (appData.debugMode)
11292               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11293             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11294                     (forwardMostMove / 2) + 1,
11295                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11296             DisplayError(move, 0);
11297             done = TRUE;
11298         } else {
11299             if (appData.debugMode)
11300               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11301                       yy_text, currentMoveString);
11302             fromX = currentMoveString[0] - AAA;
11303             fromY = currentMoveString[1] - ONE;
11304             toX = currentMoveString[2] - AAA;
11305             toY = currentMoveString[3] - ONE;
11306             promoChar = currentMoveString[4];
11307         }
11308         break;
11309
11310       case AmbiguousMove:
11311         if (appData.debugMode)
11312           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11313         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11314                 (forwardMostMove / 2) + 1,
11315                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11316         DisplayError(move, 0);
11317         done = TRUE;
11318         break;
11319
11320       default:
11321       case ImpossibleMove:
11322         if (appData.debugMode)
11323           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11324         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11325                 (forwardMostMove / 2) + 1,
11326                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11327         DisplayError(move, 0);
11328         done = TRUE;
11329         break;
11330     }
11331
11332     if (done) {
11333         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11334             DrawPosition(FALSE, boards[currentMove]);
11335             DisplayBothClocks();
11336             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11337               DisplayComment(currentMove - 1, commentList[currentMove]);
11338         }
11339         (void) StopLoadGameTimer();
11340         gameFileFP = NULL;
11341         cmailOldMove = forwardMostMove;
11342         return FALSE;
11343     } else {
11344         /* currentMoveString is set as a side-effect of yylex */
11345
11346         thinkOutput[0] = NULLCHAR;
11347         MakeMove(fromX, fromY, toX, toY, promoChar);
11348         currentMove = forwardMostMove;
11349         return TRUE;
11350     }
11351 }
11352
11353 /* Load the nth game from the given file */
11354 int
11355 LoadGameFromFile (char *filename, int n, char *title, int useList)
11356 {
11357     FILE *f;
11358     char buf[MSG_SIZ];
11359
11360     if (strcmp(filename, "-") == 0) {
11361         f = stdin;
11362         title = "stdin";
11363     } else {
11364         f = fopen(filename, "rb");
11365         if (f == NULL) {
11366           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11367             DisplayError(buf, errno);
11368             return FALSE;
11369         }
11370     }
11371     if (fseek(f, 0, 0) == -1) {
11372         /* f is not seekable; probably a pipe */
11373         useList = FALSE;
11374     }
11375     if (useList && n == 0) {
11376         int error = GameListBuild(f);
11377         if (error) {
11378             DisplayError(_("Cannot build game list"), error);
11379         } else if (!ListEmpty(&gameList) &&
11380                    ((ListGame *) gameList.tailPred)->number > 1) {
11381             GameListPopUp(f, title);
11382             return TRUE;
11383         }
11384         GameListDestroy();
11385         n = 1;
11386     }
11387     if (n == 0) n = 1;
11388     return LoadGame(f, n, title, FALSE);
11389 }
11390
11391
11392 void
11393 MakeRegisteredMove ()
11394 {
11395     int fromX, fromY, toX, toY;
11396     char promoChar;
11397     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11398         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11399           case CMAIL_MOVE:
11400           case CMAIL_DRAW:
11401             if (appData.debugMode)
11402               fprintf(debugFP, "Restoring %s for game %d\n",
11403                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11404
11405             thinkOutput[0] = NULLCHAR;
11406             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11407             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11408             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11409             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11410             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11411             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11412             MakeMove(fromX, fromY, toX, toY, promoChar);
11413             ShowMove(fromX, fromY, toX, toY);
11414
11415             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11416               case MT_NONE:
11417               case MT_CHECK:
11418                 break;
11419
11420               case MT_CHECKMATE:
11421               case MT_STAINMATE:
11422                 if (WhiteOnMove(currentMove)) {
11423                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11424                 } else {
11425                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11426                 }
11427                 break;
11428
11429               case MT_STALEMATE:
11430                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11431                 break;
11432             }
11433
11434             break;
11435
11436           case CMAIL_RESIGN:
11437             if (WhiteOnMove(currentMove)) {
11438                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11439             } else {
11440                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11441             }
11442             break;
11443
11444           case CMAIL_ACCEPT:
11445             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11446             break;
11447
11448           default:
11449             break;
11450         }
11451     }
11452
11453     return;
11454 }
11455
11456 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11457 int
11458 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11459 {
11460     int retVal;
11461
11462     if (gameNumber > nCmailGames) {
11463         DisplayError(_("No more games in this message"), 0);
11464         return FALSE;
11465     }
11466     if (f == lastLoadGameFP) {
11467         int offset = gameNumber - lastLoadGameNumber;
11468         if (offset == 0) {
11469             cmailMsg[0] = NULLCHAR;
11470             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11471                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11472                 nCmailMovesRegistered--;
11473             }
11474             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11475             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11476                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11477             }
11478         } else {
11479             if (! RegisterMove()) return FALSE;
11480         }
11481     }
11482
11483     retVal = LoadGame(f, gameNumber, title, useList);
11484
11485     /* Make move registered during previous look at this game, if any */
11486     MakeRegisteredMove();
11487
11488     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11489         commentList[currentMove]
11490           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11491         DisplayComment(currentMove - 1, commentList[currentMove]);
11492     }
11493
11494     return retVal;
11495 }
11496
11497 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11498 int
11499 ReloadGame (int offset)
11500 {
11501     int gameNumber = lastLoadGameNumber + offset;
11502     if (lastLoadGameFP == NULL) {
11503         DisplayError(_("No game has been loaded yet"), 0);
11504         return FALSE;
11505     }
11506     if (gameNumber <= 0) {
11507         DisplayError(_("Can't back up any further"), 0);
11508         return FALSE;
11509     }
11510     if (cmailMsgLoaded) {
11511         return CmailLoadGame(lastLoadGameFP, gameNumber,
11512                              lastLoadGameTitle, lastLoadGameUseList);
11513     } else {
11514         return LoadGame(lastLoadGameFP, gameNumber,
11515                         lastLoadGameTitle, lastLoadGameUseList);
11516     }
11517 }
11518
11519 int keys[EmptySquare+1];
11520
11521 int
11522 PositionMatches (Board b1, Board b2)
11523 {
11524     int r, f, sum=0;
11525     switch(appData.searchMode) {
11526         case 1: return CompareWithRights(b1, b2);
11527         case 2:
11528             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11529                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11530             }
11531             return TRUE;
11532         case 3:
11533             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11534               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11535                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11536             }
11537             return sum==0;
11538         case 4:
11539             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11540                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11541             }
11542             return sum==0;
11543     }
11544     return TRUE;
11545 }
11546
11547 #define Q_PROMO  4
11548 #define Q_EP     3
11549 #define Q_BCASTL 2
11550 #define Q_WCASTL 1
11551
11552 int pieceList[256], quickBoard[256];
11553 ChessSquare pieceType[256] = { EmptySquare };
11554 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11555 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11556 int soughtTotal, turn;
11557 Boolean epOK, flipSearch;
11558
11559 typedef struct {
11560     unsigned char piece, to;
11561 } Move;
11562
11563 #define DSIZE (250000)
11564
11565 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11566 Move *moveDatabase = initialSpace;
11567 unsigned int movePtr, dataSize = DSIZE;
11568
11569 int
11570 MakePieceList (Board board, int *counts)
11571 {
11572     int r, f, n=Q_PROMO, total=0;
11573     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11574     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11575         int sq = f + (r<<4);
11576         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11577             quickBoard[sq] = ++n;
11578             pieceList[n] = sq;
11579             pieceType[n] = board[r][f];
11580             counts[board[r][f]]++;
11581             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11582             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11583             total++;
11584         }
11585     }
11586     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11587     return total;
11588 }
11589
11590 void
11591 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11592 {
11593     int sq = fromX + (fromY<<4);
11594     int piece = quickBoard[sq];
11595     quickBoard[sq] = 0;
11596     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11597     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11598         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11599         moveDatabase[movePtr++].piece = Q_WCASTL;
11600         quickBoard[sq] = piece;
11601         piece = quickBoard[from]; quickBoard[from] = 0;
11602         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11603     } else
11604     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11605         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11606         moveDatabase[movePtr++].piece = Q_BCASTL;
11607         quickBoard[sq] = piece;
11608         piece = quickBoard[from]; quickBoard[from] = 0;
11609         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11610     } else
11611     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11612         quickBoard[(fromY<<4)+toX] = 0;
11613         moveDatabase[movePtr].piece = Q_EP;
11614         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11615         moveDatabase[movePtr].to = sq;
11616     } else
11617     if(promoPiece != pieceType[piece]) {
11618         moveDatabase[movePtr++].piece = Q_PROMO;
11619         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11620     }
11621     moveDatabase[movePtr].piece = piece;
11622     quickBoard[sq] = piece;
11623     movePtr++;
11624 }
11625
11626 int
11627 PackGame (Board board)
11628 {
11629     Move *newSpace = NULL;
11630     moveDatabase[movePtr].piece = 0; // terminate previous game
11631     if(movePtr > dataSize) {
11632         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11633         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11634         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11635         if(newSpace) {
11636             int i;
11637             Move *p = moveDatabase, *q = newSpace;
11638             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11639             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11640             moveDatabase = newSpace;
11641         } else { // calloc failed, we must be out of memory. Too bad...
11642             dataSize = 0; // prevent calloc events for all subsequent games
11643             return 0;     // and signal this one isn't cached
11644         }
11645     }
11646     movePtr++;
11647     MakePieceList(board, counts);
11648     return movePtr;
11649 }
11650
11651 int
11652 QuickCompare (Board board, int *minCounts, int *maxCounts)
11653 {   // compare according to search mode
11654     int r, f;
11655     switch(appData.searchMode)
11656     {
11657       case 1: // exact position match
11658         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11659         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11660             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11661         }
11662         break;
11663       case 2: // can have extra material on empty squares
11664         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11665             if(board[r][f] == EmptySquare) continue;
11666             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11667         }
11668         break;
11669       case 3: // material with exact Pawn structure
11670         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11671             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11672             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11673         } // fall through to material comparison
11674       case 4: // exact material
11675         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11676         break;
11677       case 6: // material range with given imbalance
11678         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11679         // fall through to range comparison
11680       case 5: // material range
11681         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11682     }
11683     return TRUE;
11684 }
11685
11686 int
11687 QuickScan (Board board, Move *move)
11688 {   // reconstruct game,and compare all positions in it
11689     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11690     do {
11691         int piece = move->piece;
11692         int to = move->to, from = pieceList[piece];
11693         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11694           if(!piece) return -1;
11695           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11696             piece = (++move)->piece;
11697             from = pieceList[piece];
11698             counts[pieceType[piece]]--;
11699             pieceType[piece] = (ChessSquare) move->to;
11700             counts[move->to]++;
11701           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11702             counts[pieceType[quickBoard[to]]]--;
11703             quickBoard[to] = 0; total--;
11704             move++;
11705             continue;
11706           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11707             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11708             from  = pieceList[piece]; // so this must be King
11709             quickBoard[from] = 0;
11710             pieceList[piece] = to;
11711             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11712             quickBoard[from] = 0; // rook
11713             quickBoard[to] = piece;
11714             to = move->to; piece = move->piece;
11715             goto aftercastle;
11716           }
11717         }
11718         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11719         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11720         quickBoard[from] = 0;
11721       aftercastle:
11722         quickBoard[to] = piece;
11723         pieceList[piece] = to;
11724         cnt++; turn ^= 3;
11725         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11726            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11727            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11728                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11729           ) {
11730             static int lastCounts[EmptySquare+1];
11731             int i;
11732             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11733             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11734         } else stretch = 0;
11735         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11736         move++;
11737     } while(1);
11738 }
11739
11740 void
11741 InitSearch ()
11742 {
11743     int r, f;
11744     flipSearch = FALSE;
11745     CopyBoard(soughtBoard, boards[currentMove]);
11746     soughtTotal = MakePieceList(soughtBoard, maxSought);
11747     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11748     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11749     CopyBoard(reverseBoard, boards[currentMove]);
11750     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11751         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11752         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11753         reverseBoard[r][f] = piece;
11754     }
11755     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11756     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11757     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11758                  || (boards[currentMove][CASTLING][2] == NoRights || 
11759                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11760                  && (boards[currentMove][CASTLING][5] == NoRights || 
11761                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11762       ) {
11763         flipSearch = TRUE;
11764         CopyBoard(flipBoard, soughtBoard);
11765         CopyBoard(rotateBoard, reverseBoard);
11766         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11767             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11768             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11769         }
11770     }
11771     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11772     if(appData.searchMode >= 5) {
11773         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11774         MakePieceList(soughtBoard, minSought);
11775         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11776     }
11777     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11778         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11779 }
11780
11781 GameInfo dummyInfo;
11782
11783 int
11784 GameContainsPosition (FILE *f, ListGame *lg)
11785 {
11786     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11787     int fromX, fromY, toX, toY;
11788     char promoChar;
11789     static int initDone=FALSE;
11790
11791     // weed out games based on numerical tag comparison
11792     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11793     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11794     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11795     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11796     if(!initDone) {
11797         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11798         initDone = TRUE;
11799     }
11800     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11801     else CopyBoard(boards[scratch], initialPosition); // default start position
11802     if(lg->moves) {
11803         turn = btm + 1;
11804         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11805         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11806     }
11807     if(btm) plyNr++;
11808     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11809     fseek(f, lg->offset, 0);
11810     yynewfile(f);
11811     while(1) {
11812         yyboardindex = scratch;
11813         quickFlag = plyNr+1;
11814         next = Myylex();
11815         quickFlag = 0;
11816         switch(next) {
11817             case PGNTag:
11818                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11819             default:
11820                 continue;
11821
11822             case XBoardGame:
11823             case GNUChessGame:
11824                 if(plyNr) return -1; // after we have seen moves, this is for new game
11825               continue;
11826
11827             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11828             case ImpossibleMove:
11829             case WhiteWins: // game ends here with these four
11830             case BlackWins:
11831             case GameIsDrawn:
11832             case GameUnfinished:
11833                 return -1;
11834
11835             case IllegalMove:
11836                 if(appData.testLegality) return -1;
11837             case WhiteCapturesEnPassant:
11838             case BlackCapturesEnPassant:
11839             case WhitePromotion:
11840             case BlackPromotion:
11841             case WhiteNonPromotion:
11842             case BlackNonPromotion:
11843             case NormalMove:
11844             case WhiteKingSideCastle:
11845             case WhiteQueenSideCastle:
11846             case BlackKingSideCastle:
11847             case BlackQueenSideCastle:
11848             case WhiteKingSideCastleWild:
11849             case WhiteQueenSideCastleWild:
11850             case BlackKingSideCastleWild:
11851             case BlackQueenSideCastleWild:
11852             case WhiteHSideCastleFR:
11853             case WhiteASideCastleFR:
11854             case BlackHSideCastleFR:
11855             case BlackASideCastleFR:
11856                 fromX = currentMoveString[0] - AAA;
11857                 fromY = currentMoveString[1] - ONE;
11858                 toX = currentMoveString[2] - AAA;
11859                 toY = currentMoveString[3] - ONE;
11860                 promoChar = currentMoveString[4];
11861                 break;
11862             case WhiteDrop:
11863             case BlackDrop:
11864                 fromX = next == WhiteDrop ?
11865                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11866                   (int) CharToPiece(ToLower(currentMoveString[0]));
11867                 fromY = DROP_RANK;
11868                 toX = currentMoveString[2] - AAA;
11869                 toY = currentMoveString[3] - ONE;
11870                 promoChar = 0;
11871                 break;
11872         }
11873         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11874         plyNr++;
11875         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11876         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11877         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11878         if(appData.findMirror) {
11879             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11880             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11881         }
11882     }
11883 }
11884
11885 /* Load the nth game from open file f */
11886 int
11887 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11888 {
11889     ChessMove cm;
11890     char buf[MSG_SIZ];
11891     int gn = gameNumber;
11892     ListGame *lg = NULL;
11893     int numPGNTags = 0;
11894     int err, pos = -1;
11895     GameMode oldGameMode;
11896     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11897
11898     if (appData.debugMode)
11899         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11900
11901     if (gameMode == Training )
11902         SetTrainingModeOff();
11903
11904     oldGameMode = gameMode;
11905     if (gameMode != BeginningOfGame) {
11906       Reset(FALSE, TRUE);
11907     }
11908
11909     gameFileFP = f;
11910     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11911         fclose(lastLoadGameFP);
11912     }
11913
11914     if (useList) {
11915         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11916
11917         if (lg) {
11918             fseek(f, lg->offset, 0);
11919             GameListHighlight(gameNumber);
11920             pos = lg->position;
11921             gn = 1;
11922         }
11923         else {
11924             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11925               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11926             else
11927             DisplayError(_("Game number out of range"), 0);
11928             return FALSE;
11929         }
11930     } else {
11931         GameListDestroy();
11932         if (fseek(f, 0, 0) == -1) {
11933             if (f == lastLoadGameFP ?
11934                 gameNumber == lastLoadGameNumber + 1 :
11935                 gameNumber == 1) {
11936                 gn = 1;
11937             } else {
11938                 DisplayError(_("Can't seek on game file"), 0);
11939                 return FALSE;
11940             }
11941         }
11942     }
11943     lastLoadGameFP = f;
11944     lastLoadGameNumber = gameNumber;
11945     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11946     lastLoadGameUseList = useList;
11947
11948     yynewfile(f);
11949
11950     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11951       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11952                 lg->gameInfo.black);
11953             DisplayTitle(buf);
11954     } else if (*title != NULLCHAR) {
11955         if (gameNumber > 1) {
11956           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11957             DisplayTitle(buf);
11958         } else {
11959             DisplayTitle(title);
11960         }
11961     }
11962
11963     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11964         gameMode = PlayFromGameFile;
11965         ModeHighlight();
11966     }
11967
11968     currentMove = forwardMostMove = backwardMostMove = 0;
11969     CopyBoard(boards[0], initialPosition);
11970     StopClocks();
11971
11972     /*
11973      * Skip the first gn-1 games in the file.
11974      * Also skip over anything that precedes an identifiable
11975      * start of game marker, to avoid being confused by
11976      * garbage at the start of the file.  Currently
11977      * recognized start of game markers are the move number "1",
11978      * the pattern "gnuchess .* game", the pattern
11979      * "^[#;%] [^ ]* game file", and a PGN tag block.
11980      * A game that starts with one of the latter two patterns
11981      * will also have a move number 1, possibly
11982      * following a position diagram.
11983      * 5-4-02: Let's try being more lenient and allowing a game to
11984      * start with an unnumbered move.  Does that break anything?
11985      */
11986     cm = lastLoadGameStart = EndOfFile;
11987     while (gn > 0) {
11988         yyboardindex = forwardMostMove;
11989         cm = (ChessMove) Myylex();
11990         switch (cm) {
11991           case EndOfFile:
11992             if (cmailMsgLoaded) {
11993                 nCmailGames = CMAIL_MAX_GAMES - gn;
11994             } else {
11995                 Reset(TRUE, TRUE);
11996                 DisplayError(_("Game not found in file"), 0);
11997             }
11998             return FALSE;
11999
12000           case GNUChessGame:
12001           case XBoardGame:
12002             gn--;
12003             lastLoadGameStart = cm;
12004             break;
12005
12006           case MoveNumberOne:
12007             switch (lastLoadGameStart) {
12008               case GNUChessGame:
12009               case XBoardGame:
12010               case PGNTag:
12011                 break;
12012               case MoveNumberOne:
12013               case EndOfFile:
12014                 gn--;           /* count this game */
12015                 lastLoadGameStart = cm;
12016                 break;
12017               default:
12018                 /* impossible */
12019                 break;
12020             }
12021             break;
12022
12023           case PGNTag:
12024             switch (lastLoadGameStart) {
12025               case GNUChessGame:
12026               case PGNTag:
12027               case MoveNumberOne:
12028               case EndOfFile:
12029                 gn--;           /* count this game */
12030                 lastLoadGameStart = cm;
12031                 break;
12032               case XBoardGame:
12033                 lastLoadGameStart = cm; /* game counted already */
12034                 break;
12035               default:
12036                 /* impossible */
12037                 break;
12038             }
12039             if (gn > 0) {
12040                 do {
12041                     yyboardindex = forwardMostMove;
12042                     cm = (ChessMove) Myylex();
12043                 } while (cm == PGNTag || cm == Comment);
12044             }
12045             break;
12046
12047           case WhiteWins:
12048           case BlackWins:
12049           case GameIsDrawn:
12050             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12051                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12052                     != CMAIL_OLD_RESULT) {
12053                     nCmailResults ++ ;
12054                     cmailResult[  CMAIL_MAX_GAMES
12055                                 - gn - 1] = CMAIL_OLD_RESULT;
12056                 }
12057             }
12058             break;
12059
12060           case NormalMove:
12061             /* Only a NormalMove can be at the start of a game
12062              * without a position diagram. */
12063             if (lastLoadGameStart == EndOfFile ) {
12064               gn--;
12065               lastLoadGameStart = MoveNumberOne;
12066             }
12067             break;
12068
12069           default:
12070             break;
12071         }
12072     }
12073
12074     if (appData.debugMode)
12075       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12076
12077     if (cm == XBoardGame) {
12078         /* Skip any header junk before position diagram and/or move 1 */
12079         for (;;) {
12080             yyboardindex = forwardMostMove;
12081             cm = (ChessMove) Myylex();
12082
12083             if (cm == EndOfFile ||
12084                 cm == GNUChessGame || cm == XBoardGame) {
12085                 /* Empty game; pretend end-of-file and handle later */
12086                 cm = EndOfFile;
12087                 break;
12088             }
12089
12090             if (cm == MoveNumberOne || cm == PositionDiagram ||
12091                 cm == PGNTag || cm == Comment)
12092               break;
12093         }
12094     } else if (cm == GNUChessGame) {
12095         if (gameInfo.event != NULL) {
12096             free(gameInfo.event);
12097         }
12098         gameInfo.event = StrSave(yy_text);
12099     }
12100
12101     startedFromSetupPosition = FALSE;
12102     while (cm == PGNTag) {
12103         if (appData.debugMode)
12104           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12105         err = ParsePGNTag(yy_text, &gameInfo);
12106         if (!err) numPGNTags++;
12107
12108         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12109         if(gameInfo.variant != oldVariant) {
12110             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12111             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12112             InitPosition(TRUE);
12113             oldVariant = gameInfo.variant;
12114             if (appData.debugMode)
12115               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12116         }
12117
12118
12119         if (gameInfo.fen != NULL) {
12120           Board initial_position;
12121           startedFromSetupPosition = TRUE;
12122           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12123             Reset(TRUE, TRUE);
12124             DisplayError(_("Bad FEN position in file"), 0);
12125             return FALSE;
12126           }
12127           CopyBoard(boards[0], initial_position);
12128           if (blackPlaysFirst) {
12129             currentMove = forwardMostMove = backwardMostMove = 1;
12130             CopyBoard(boards[1], initial_position);
12131             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12132             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12133             timeRemaining[0][1] = whiteTimeRemaining;
12134             timeRemaining[1][1] = blackTimeRemaining;
12135             if (commentList[0] != NULL) {
12136               commentList[1] = commentList[0];
12137               commentList[0] = NULL;
12138             }
12139           } else {
12140             currentMove = forwardMostMove = backwardMostMove = 0;
12141           }
12142           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12143           {   int i;
12144               initialRulePlies = FENrulePlies;
12145               for( i=0; i< nrCastlingRights; i++ )
12146                   initialRights[i] = initial_position[CASTLING][i];
12147           }
12148           yyboardindex = forwardMostMove;
12149           free(gameInfo.fen);
12150           gameInfo.fen = NULL;
12151         }
12152
12153         yyboardindex = forwardMostMove;
12154         cm = (ChessMove) Myylex();
12155
12156         /* Handle comments interspersed among the tags */
12157         while (cm == Comment) {
12158             char *p;
12159             if (appData.debugMode)
12160               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12161             p = yy_text;
12162             AppendComment(currentMove, p, FALSE);
12163             yyboardindex = forwardMostMove;
12164             cm = (ChessMove) Myylex();
12165         }
12166     }
12167
12168     /* don't rely on existence of Event tag since if game was
12169      * pasted from clipboard the Event tag may not exist
12170      */
12171     if (numPGNTags > 0){
12172         char *tags;
12173         if (gameInfo.variant == VariantNormal) {
12174           VariantClass v = StringToVariant(gameInfo.event);
12175           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12176           if(v < VariantShogi) gameInfo.variant = v;
12177         }
12178         if (!matchMode) {
12179           if( appData.autoDisplayTags ) {
12180             tags = PGNTags(&gameInfo);
12181             TagsPopUp(tags, CmailMsg());
12182             free(tags);
12183           }
12184         }
12185     } else {
12186         /* Make something up, but don't display it now */
12187         SetGameInfo();
12188         TagsPopDown();
12189     }
12190
12191     if (cm == PositionDiagram) {
12192         int i, j;
12193         char *p;
12194         Board initial_position;
12195
12196         if (appData.debugMode)
12197           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12198
12199         if (!startedFromSetupPosition) {
12200             p = yy_text;
12201             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12202               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12203                 switch (*p) {
12204                   case '{':
12205                   case '[':
12206                   case '-':
12207                   case ' ':
12208                   case '\t':
12209                   case '\n':
12210                   case '\r':
12211                     break;
12212                   default:
12213                     initial_position[i][j++] = CharToPiece(*p);
12214                     break;
12215                 }
12216             while (*p == ' ' || *p == '\t' ||
12217                    *p == '\n' || *p == '\r') p++;
12218
12219             if (strncmp(p, "black", strlen("black"))==0)
12220               blackPlaysFirst = TRUE;
12221             else
12222               blackPlaysFirst = FALSE;
12223             startedFromSetupPosition = TRUE;
12224
12225             CopyBoard(boards[0], initial_position);
12226             if (blackPlaysFirst) {
12227                 currentMove = forwardMostMove = backwardMostMove = 1;
12228                 CopyBoard(boards[1], initial_position);
12229                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12230                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12231                 timeRemaining[0][1] = whiteTimeRemaining;
12232                 timeRemaining[1][1] = blackTimeRemaining;
12233                 if (commentList[0] != NULL) {
12234                     commentList[1] = commentList[0];
12235                     commentList[0] = NULL;
12236                 }
12237             } else {
12238                 currentMove = forwardMostMove = backwardMostMove = 0;
12239             }
12240         }
12241         yyboardindex = forwardMostMove;
12242         cm = (ChessMove) Myylex();
12243     }
12244
12245     if (first.pr == NoProc) {
12246         StartChessProgram(&first);
12247     }
12248     InitChessProgram(&first, FALSE);
12249     SendToProgram("force\n", &first);
12250     if (startedFromSetupPosition) {
12251         SendBoard(&first, forwardMostMove);
12252     if (appData.debugMode) {
12253         fprintf(debugFP, "Load Game\n");
12254     }
12255         DisplayBothClocks();
12256     }
12257
12258     /* [HGM] server: flag to write setup moves in broadcast file as one */
12259     loadFlag = appData.suppressLoadMoves;
12260
12261     while (cm == Comment) {
12262         char *p;
12263         if (appData.debugMode)
12264           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12265         p = yy_text;
12266         AppendComment(currentMove, p, FALSE);
12267         yyboardindex = forwardMostMove;
12268         cm = (ChessMove) Myylex();
12269     }
12270
12271     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12272         cm == WhiteWins || cm == BlackWins ||
12273         cm == GameIsDrawn || cm == GameUnfinished) {
12274         DisplayMessage("", _("No moves in game"));
12275         if (cmailMsgLoaded) {
12276             if (appData.debugMode)
12277               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12278             ClearHighlights();
12279             flipView = FALSE;
12280         }
12281         DrawPosition(FALSE, boards[currentMove]);
12282         DisplayBothClocks();
12283         gameMode = EditGame;
12284         ModeHighlight();
12285         gameFileFP = NULL;
12286         cmailOldMove = 0;
12287         return TRUE;
12288     }
12289
12290     // [HGM] PV info: routine tests if comment empty
12291     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12292         DisplayComment(currentMove - 1, commentList[currentMove]);
12293     }
12294     if (!matchMode && appData.timeDelay != 0)
12295       DrawPosition(FALSE, boards[currentMove]);
12296
12297     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12298       programStats.ok_to_send = 1;
12299     }
12300
12301     /* if the first token after the PGN tags is a move
12302      * and not move number 1, retrieve it from the parser
12303      */
12304     if (cm != MoveNumberOne)
12305         LoadGameOneMove(cm);
12306
12307     /* load the remaining moves from the file */
12308     while (LoadGameOneMove(EndOfFile)) {
12309       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12310       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12311     }
12312
12313     /* rewind to the start of the game */
12314     currentMove = backwardMostMove;
12315
12316     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12317
12318     if (oldGameMode == AnalyzeFile ||
12319         oldGameMode == AnalyzeMode) {
12320       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12321       keepInfo = 1;
12322       AnalyzeFileEvent();
12323       keepInfo = 0;
12324     }
12325
12326     if (!matchMode && pos > 0) {
12327         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12328     } else
12329     if (matchMode || appData.timeDelay == 0) {
12330       ToEndEvent();
12331     } else if (appData.timeDelay > 0) {
12332       AutoPlayGameLoop();
12333     }
12334
12335     if (appData.debugMode)
12336         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12337
12338     loadFlag = 0; /* [HGM] true game starts */
12339     return TRUE;
12340 }
12341
12342 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12343 int
12344 ReloadPosition (int offset)
12345 {
12346     int positionNumber = lastLoadPositionNumber + offset;
12347     if (lastLoadPositionFP == NULL) {
12348         DisplayError(_("No position has been loaded yet"), 0);
12349         return FALSE;
12350     }
12351     if (positionNumber <= 0) {
12352         DisplayError(_("Can't back up any further"), 0);
12353         return FALSE;
12354     }
12355     return LoadPosition(lastLoadPositionFP, positionNumber,
12356                         lastLoadPositionTitle);
12357 }
12358
12359 /* Load the nth position from the given file */
12360 int
12361 LoadPositionFromFile (char *filename, int n, char *title)
12362 {
12363     FILE *f;
12364     char buf[MSG_SIZ];
12365
12366     if (strcmp(filename, "-") == 0) {
12367         return LoadPosition(stdin, n, "stdin");
12368     } else {
12369         f = fopen(filename, "rb");
12370         if (f == NULL) {
12371             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12372             DisplayError(buf, errno);
12373             return FALSE;
12374         } else {
12375             return LoadPosition(f, n, title);
12376         }
12377     }
12378 }
12379
12380 /* Load the nth position from the given open file, and close it */
12381 int
12382 LoadPosition (FILE *f, int positionNumber, char *title)
12383 {
12384     char *p, line[MSG_SIZ];
12385     Board initial_position;
12386     int i, j, fenMode, pn;
12387
12388     if (gameMode == Training )
12389         SetTrainingModeOff();
12390
12391     if (gameMode != BeginningOfGame) {
12392         Reset(FALSE, TRUE);
12393     }
12394     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12395         fclose(lastLoadPositionFP);
12396     }
12397     if (positionNumber == 0) positionNumber = 1;
12398     lastLoadPositionFP = f;
12399     lastLoadPositionNumber = positionNumber;
12400     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12401     if (first.pr == NoProc && !appData.noChessProgram) {
12402       StartChessProgram(&first);
12403       InitChessProgram(&first, FALSE);
12404     }
12405     pn = positionNumber;
12406     if (positionNumber < 0) {
12407         /* Negative position number means to seek to that byte offset */
12408         if (fseek(f, -positionNumber, 0) == -1) {
12409             DisplayError(_("Can't seek on position file"), 0);
12410             return FALSE;
12411         };
12412         pn = 1;
12413     } else {
12414         if (fseek(f, 0, 0) == -1) {
12415             if (f == lastLoadPositionFP ?
12416                 positionNumber == lastLoadPositionNumber + 1 :
12417                 positionNumber == 1) {
12418                 pn = 1;
12419             } else {
12420                 DisplayError(_("Can't seek on position file"), 0);
12421                 return FALSE;
12422             }
12423         }
12424     }
12425     /* See if this file is FEN or old-style xboard */
12426     if (fgets(line, MSG_SIZ, f) == NULL) {
12427         DisplayError(_("Position not found in file"), 0);
12428         return FALSE;
12429     }
12430     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12431     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12432
12433     if (pn >= 2) {
12434         if (fenMode || line[0] == '#') pn--;
12435         while (pn > 0) {
12436             /* skip positions before number pn */
12437             if (fgets(line, MSG_SIZ, f) == NULL) {
12438                 Reset(TRUE, TRUE);
12439                 DisplayError(_("Position not found in file"), 0);
12440                 return FALSE;
12441             }
12442             if (fenMode || line[0] == '#') pn--;
12443         }
12444     }
12445
12446     if (fenMode) {
12447         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12448             DisplayError(_("Bad FEN position in file"), 0);
12449             return FALSE;
12450         }
12451     } else {
12452         (void) fgets(line, MSG_SIZ, f);
12453         (void) fgets(line, MSG_SIZ, f);
12454
12455         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12456             (void) fgets(line, MSG_SIZ, f);
12457             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12458                 if (*p == ' ')
12459                   continue;
12460                 initial_position[i][j++] = CharToPiece(*p);
12461             }
12462         }
12463
12464         blackPlaysFirst = FALSE;
12465         if (!feof(f)) {
12466             (void) fgets(line, MSG_SIZ, f);
12467             if (strncmp(line, "black", strlen("black"))==0)
12468               blackPlaysFirst = TRUE;
12469         }
12470     }
12471     startedFromSetupPosition = TRUE;
12472
12473     CopyBoard(boards[0], initial_position);
12474     if (blackPlaysFirst) {
12475         currentMove = forwardMostMove = backwardMostMove = 1;
12476         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12477         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12478         CopyBoard(boards[1], initial_position);
12479         DisplayMessage("", _("Black to play"));
12480     } else {
12481         currentMove = forwardMostMove = backwardMostMove = 0;
12482         DisplayMessage("", _("White to play"));
12483     }
12484     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12485     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12486         SendToProgram("force\n", &first);
12487         SendBoard(&first, forwardMostMove);
12488     }
12489     if (appData.debugMode) {
12490 int i, j;
12491   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12492   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12493         fprintf(debugFP, "Load Position\n");
12494     }
12495
12496     if (positionNumber > 1) {
12497       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12498         DisplayTitle(line);
12499     } else {
12500         DisplayTitle(title);
12501     }
12502     gameMode = EditGame;
12503     ModeHighlight();
12504     ResetClocks();
12505     timeRemaining[0][1] = whiteTimeRemaining;
12506     timeRemaining[1][1] = blackTimeRemaining;
12507     DrawPosition(FALSE, boards[currentMove]);
12508
12509     return TRUE;
12510 }
12511
12512
12513 void
12514 CopyPlayerNameIntoFileName (char **dest, char *src)
12515 {
12516     while (*src != NULLCHAR && *src != ',') {
12517         if (*src == ' ') {
12518             *(*dest)++ = '_';
12519             src++;
12520         } else {
12521             *(*dest)++ = *src++;
12522         }
12523     }
12524 }
12525
12526 char *
12527 DefaultFileName (char *ext)
12528 {
12529     static char def[MSG_SIZ];
12530     char *p;
12531
12532     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12533         p = def;
12534         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12535         *p++ = '-';
12536         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12537         *p++ = '.';
12538         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12539     } else {
12540         def[0] = NULLCHAR;
12541     }
12542     return def;
12543 }
12544
12545 /* Save the current game to the given file */
12546 int
12547 SaveGameToFile (char *filename, int append)
12548 {
12549     FILE *f;
12550     char buf[MSG_SIZ];
12551     int result, i, t,tot=0;
12552
12553     if (strcmp(filename, "-") == 0) {
12554         return SaveGame(stdout, 0, NULL);
12555     } else {
12556         for(i=0; i<10; i++) { // upto 10 tries
12557              f = fopen(filename, append ? "a" : "w");
12558              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12559              if(f || errno != 13) break;
12560              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12561              tot += t;
12562         }
12563         if (f == NULL) {
12564             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12565             DisplayError(buf, errno);
12566             return FALSE;
12567         } else {
12568             safeStrCpy(buf, lastMsg, MSG_SIZ);
12569             DisplayMessage(_("Waiting for access to save file"), "");
12570             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12571             DisplayMessage(_("Saving game"), "");
12572             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12573             result = SaveGame(f, 0, NULL);
12574             DisplayMessage(buf, "");
12575             return result;
12576         }
12577     }
12578 }
12579
12580 char *
12581 SavePart (char *str)
12582 {
12583     static char buf[MSG_SIZ];
12584     char *p;
12585
12586     p = strchr(str, ' ');
12587     if (p == NULL) return str;
12588     strncpy(buf, str, p - str);
12589     buf[p - str] = NULLCHAR;
12590     return buf;
12591 }
12592
12593 #define PGN_MAX_LINE 75
12594
12595 #define PGN_SIDE_WHITE  0
12596 #define PGN_SIDE_BLACK  1
12597
12598 static int
12599 FindFirstMoveOutOfBook (int side)
12600 {
12601     int result = -1;
12602
12603     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12604         int index = backwardMostMove;
12605         int has_book_hit = 0;
12606
12607         if( (index % 2) != side ) {
12608             index++;
12609         }
12610
12611         while( index < forwardMostMove ) {
12612             /* Check to see if engine is in book */
12613             int depth = pvInfoList[index].depth;
12614             int score = pvInfoList[index].score;
12615             int in_book = 0;
12616
12617             if( depth <= 2 ) {
12618                 in_book = 1;
12619             }
12620             else if( score == 0 && depth == 63 ) {
12621                 in_book = 1; /* Zappa */
12622             }
12623             else if( score == 2 && depth == 99 ) {
12624                 in_book = 1; /* Abrok */
12625             }
12626
12627             has_book_hit += in_book;
12628
12629             if( ! in_book ) {
12630                 result = index;
12631
12632                 break;
12633             }
12634
12635             index += 2;
12636         }
12637     }
12638
12639     return result;
12640 }
12641
12642 void
12643 GetOutOfBookInfo (char * buf)
12644 {
12645     int oob[2];
12646     int i;
12647     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12648
12649     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12650     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12651
12652     *buf = '\0';
12653
12654     if( oob[0] >= 0 || oob[1] >= 0 ) {
12655         for( i=0; i<2; i++ ) {
12656             int idx = oob[i];
12657
12658             if( idx >= 0 ) {
12659                 if( i > 0 && oob[0] >= 0 ) {
12660                     strcat( buf, "   " );
12661                 }
12662
12663                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12664                 sprintf( buf+strlen(buf), "%s%.2f",
12665                     pvInfoList[idx].score >= 0 ? "+" : "",
12666                     pvInfoList[idx].score / 100.0 );
12667             }
12668         }
12669     }
12670 }
12671
12672 /* Save game in PGN style and close the file */
12673 int
12674 SaveGamePGN (FILE *f)
12675 {
12676     int i, offset, linelen, newblock;
12677 //    char *movetext;
12678     char numtext[32];
12679     int movelen, numlen, blank;
12680     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12681
12682     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12683
12684     PrintPGNTags(f, &gameInfo);
12685
12686     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12687
12688     if (backwardMostMove > 0 || startedFromSetupPosition) {
12689         char *fen = PositionToFEN(backwardMostMove, NULL);
12690         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12691         fprintf(f, "\n{--------------\n");
12692         PrintPosition(f, backwardMostMove);
12693         fprintf(f, "--------------}\n");
12694         free(fen);
12695     }
12696     else {
12697         /* [AS] Out of book annotation */
12698         if( appData.saveOutOfBookInfo ) {
12699             char buf[64];
12700
12701             GetOutOfBookInfo( buf );
12702
12703             if( buf[0] != '\0' ) {
12704                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12705             }
12706         }
12707
12708         fprintf(f, "\n");
12709     }
12710
12711     i = backwardMostMove;
12712     linelen = 0;
12713     newblock = TRUE;
12714
12715     while (i < forwardMostMove) {
12716         /* Print comments preceding this move */
12717         if (commentList[i] != NULL) {
12718             if (linelen > 0) fprintf(f, "\n");
12719             fprintf(f, "%s", commentList[i]);
12720             linelen = 0;
12721             newblock = TRUE;
12722         }
12723
12724         /* Format move number */
12725         if ((i % 2) == 0)
12726           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12727         else
12728           if (newblock)
12729             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12730           else
12731             numtext[0] = NULLCHAR;
12732
12733         numlen = strlen(numtext);
12734         newblock = FALSE;
12735
12736         /* Print move number */
12737         blank = linelen > 0 && numlen > 0;
12738         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12739             fprintf(f, "\n");
12740             linelen = 0;
12741             blank = 0;
12742         }
12743         if (blank) {
12744             fprintf(f, " ");
12745             linelen++;
12746         }
12747         fprintf(f, "%s", numtext);
12748         linelen += numlen;
12749
12750         /* Get move */
12751         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12752         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12753
12754         /* Print move */
12755         blank = linelen > 0 && movelen > 0;
12756         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12757             fprintf(f, "\n");
12758             linelen = 0;
12759             blank = 0;
12760         }
12761         if (blank) {
12762             fprintf(f, " ");
12763             linelen++;
12764         }
12765         fprintf(f, "%s", move_buffer);
12766         linelen += movelen;
12767
12768         /* [AS] Add PV info if present */
12769         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12770             /* [HGM] add time */
12771             char buf[MSG_SIZ]; int seconds;
12772
12773             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12774
12775             if( seconds <= 0)
12776               buf[0] = 0;
12777             else
12778               if( seconds < 30 )
12779                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12780               else
12781                 {
12782                   seconds = (seconds + 4)/10; // round to full seconds
12783                   if( seconds < 60 )
12784                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12785                   else
12786                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12787                 }
12788
12789             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12790                       pvInfoList[i].score >= 0 ? "+" : "",
12791                       pvInfoList[i].score / 100.0,
12792                       pvInfoList[i].depth,
12793                       buf );
12794
12795             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12796
12797             /* Print score/depth */
12798             blank = linelen > 0 && movelen > 0;
12799             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12800                 fprintf(f, "\n");
12801                 linelen = 0;
12802                 blank = 0;
12803             }
12804             if (blank) {
12805                 fprintf(f, " ");
12806                 linelen++;
12807             }
12808             fprintf(f, "%s", move_buffer);
12809             linelen += movelen;
12810         }
12811
12812         i++;
12813     }
12814
12815     /* Start a new line */
12816     if (linelen > 0) fprintf(f, "\n");
12817
12818     /* Print comments after last move */
12819     if (commentList[i] != NULL) {
12820         fprintf(f, "%s\n", commentList[i]);
12821     }
12822
12823     /* Print result */
12824     if (gameInfo.resultDetails != NULL &&
12825         gameInfo.resultDetails[0] != NULLCHAR) {
12826         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12827                 PGNResult(gameInfo.result));
12828     } else {
12829         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12830     }
12831
12832     fclose(f);
12833     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12834     return TRUE;
12835 }
12836
12837 /* Save game in old style and close the file */
12838 int
12839 SaveGameOldStyle (FILE *f)
12840 {
12841     int i, offset;
12842     time_t tm;
12843
12844     tm = time((time_t *) NULL);
12845
12846     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12847     PrintOpponents(f);
12848
12849     if (backwardMostMove > 0 || startedFromSetupPosition) {
12850         fprintf(f, "\n[--------------\n");
12851         PrintPosition(f, backwardMostMove);
12852         fprintf(f, "--------------]\n");
12853     } else {
12854         fprintf(f, "\n");
12855     }
12856
12857     i = backwardMostMove;
12858     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12859
12860     while (i < forwardMostMove) {
12861         if (commentList[i] != NULL) {
12862             fprintf(f, "[%s]\n", commentList[i]);
12863         }
12864
12865         if ((i % 2) == 1) {
12866             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12867             i++;
12868         } else {
12869             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12870             i++;
12871             if (commentList[i] != NULL) {
12872                 fprintf(f, "\n");
12873                 continue;
12874             }
12875             if (i >= forwardMostMove) {
12876                 fprintf(f, "\n");
12877                 break;
12878             }
12879             fprintf(f, "%s\n", parseList[i]);
12880             i++;
12881         }
12882     }
12883
12884     if (commentList[i] != NULL) {
12885         fprintf(f, "[%s]\n", commentList[i]);
12886     }
12887
12888     /* This isn't really the old style, but it's close enough */
12889     if (gameInfo.resultDetails != NULL &&
12890         gameInfo.resultDetails[0] != NULLCHAR) {
12891         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12892                 gameInfo.resultDetails);
12893     } else {
12894         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12895     }
12896
12897     fclose(f);
12898     return TRUE;
12899 }
12900
12901 /* Save the current game to open file f and close the file */
12902 int
12903 SaveGame (FILE *f, int dummy, char *dummy2)
12904 {
12905     if (gameMode == EditPosition) EditPositionDone(TRUE);
12906     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12907     if (appData.oldSaveStyle)
12908       return SaveGameOldStyle(f);
12909     else
12910       return SaveGamePGN(f);
12911 }
12912
12913 /* Save the current position to the given file */
12914 int
12915 SavePositionToFile (char *filename)
12916 {
12917     FILE *f;
12918     char buf[MSG_SIZ];
12919
12920     if (strcmp(filename, "-") == 0) {
12921         return SavePosition(stdout, 0, NULL);
12922     } else {
12923         f = fopen(filename, "a");
12924         if (f == NULL) {
12925             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12926             DisplayError(buf, errno);
12927             return FALSE;
12928         } else {
12929             safeStrCpy(buf, lastMsg, MSG_SIZ);
12930             DisplayMessage(_("Waiting for access to save file"), "");
12931             flock(fileno(f), LOCK_EX); // [HGM] lock
12932             DisplayMessage(_("Saving position"), "");
12933             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12934             SavePosition(f, 0, NULL);
12935             DisplayMessage(buf, "");
12936             return TRUE;
12937         }
12938     }
12939 }
12940
12941 /* Save the current position to the given open file and close the file */
12942 int
12943 SavePosition (FILE *f, int dummy, char *dummy2)
12944 {
12945     time_t tm;
12946     char *fen;
12947
12948     if (gameMode == EditPosition) EditPositionDone(TRUE);
12949     if (appData.oldSaveStyle) {
12950         tm = time((time_t *) NULL);
12951
12952         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12953         PrintOpponents(f);
12954         fprintf(f, "[--------------\n");
12955         PrintPosition(f, currentMove);
12956         fprintf(f, "--------------]\n");
12957     } else {
12958         fen = PositionToFEN(currentMove, NULL);
12959         fprintf(f, "%s\n", fen);
12960         free(fen);
12961     }
12962     fclose(f);
12963     return TRUE;
12964 }
12965
12966 void
12967 ReloadCmailMsgEvent (int unregister)
12968 {
12969 #if !WIN32
12970     static char *inFilename = NULL;
12971     static char *outFilename;
12972     int i;
12973     struct stat inbuf, outbuf;
12974     int status;
12975
12976     /* Any registered moves are unregistered if unregister is set, */
12977     /* i.e. invoked by the signal handler */
12978     if (unregister) {
12979         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12980             cmailMoveRegistered[i] = FALSE;
12981             if (cmailCommentList[i] != NULL) {
12982                 free(cmailCommentList[i]);
12983                 cmailCommentList[i] = NULL;
12984             }
12985         }
12986         nCmailMovesRegistered = 0;
12987     }
12988
12989     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12990         cmailResult[i] = CMAIL_NOT_RESULT;
12991     }
12992     nCmailResults = 0;
12993
12994     if (inFilename == NULL) {
12995         /* Because the filenames are static they only get malloced once  */
12996         /* and they never get freed                                      */
12997         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12998         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12999
13000         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13001         sprintf(outFilename, "%s.out", appData.cmailGameName);
13002     }
13003
13004     status = stat(outFilename, &outbuf);
13005     if (status < 0) {
13006         cmailMailedMove = FALSE;
13007     } else {
13008         status = stat(inFilename, &inbuf);
13009         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13010     }
13011
13012     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13013        counts the games, notes how each one terminated, etc.
13014
13015        It would be nice to remove this kludge and instead gather all
13016        the information while building the game list.  (And to keep it
13017        in the game list nodes instead of having a bunch of fixed-size
13018        parallel arrays.)  Note this will require getting each game's
13019        termination from the PGN tags, as the game list builder does
13020        not process the game moves.  --mann
13021        */
13022     cmailMsgLoaded = TRUE;
13023     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13024
13025     /* Load first game in the file or popup game menu */
13026     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13027
13028 #endif /* !WIN32 */
13029     return;
13030 }
13031
13032 int
13033 RegisterMove ()
13034 {
13035     FILE *f;
13036     char string[MSG_SIZ];
13037
13038     if (   cmailMailedMove
13039         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13040         return TRUE;            /* Allow free viewing  */
13041     }
13042
13043     /* Unregister move to ensure that we don't leave RegisterMove        */
13044     /* with the move registered when the conditions for registering no   */
13045     /* longer hold                                                       */
13046     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13047         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13048         nCmailMovesRegistered --;
13049
13050         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13051           {
13052               free(cmailCommentList[lastLoadGameNumber - 1]);
13053               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13054           }
13055     }
13056
13057     if (cmailOldMove == -1) {
13058         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13059         return FALSE;
13060     }
13061
13062     if (currentMove > cmailOldMove + 1) {
13063         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13064         return FALSE;
13065     }
13066
13067     if (currentMove < cmailOldMove) {
13068         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13069         return FALSE;
13070     }
13071
13072     if (forwardMostMove > currentMove) {
13073         /* Silently truncate extra moves */
13074         TruncateGame();
13075     }
13076
13077     if (   (currentMove == cmailOldMove + 1)
13078         || (   (currentMove == cmailOldMove)
13079             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13080                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13081         if (gameInfo.result != GameUnfinished) {
13082             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13083         }
13084
13085         if (commentList[currentMove] != NULL) {
13086             cmailCommentList[lastLoadGameNumber - 1]
13087               = StrSave(commentList[currentMove]);
13088         }
13089         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13090
13091         if (appData.debugMode)
13092           fprintf(debugFP, "Saving %s for game %d\n",
13093                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13094
13095         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13096
13097         f = fopen(string, "w");
13098         if (appData.oldSaveStyle) {
13099             SaveGameOldStyle(f); /* also closes the file */
13100
13101             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13102             f = fopen(string, "w");
13103             SavePosition(f, 0, NULL); /* also closes the file */
13104         } else {
13105             fprintf(f, "{--------------\n");
13106             PrintPosition(f, currentMove);
13107             fprintf(f, "--------------}\n\n");
13108
13109             SaveGame(f, 0, NULL); /* also closes the file*/
13110         }
13111
13112         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13113         nCmailMovesRegistered ++;
13114     } else if (nCmailGames == 1) {
13115         DisplayError(_("You have not made a move yet"), 0);
13116         return FALSE;
13117     }
13118
13119     return TRUE;
13120 }
13121
13122 void
13123 MailMoveEvent ()
13124 {
13125 #if !WIN32
13126     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13127     FILE *commandOutput;
13128     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13129     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13130     int nBuffers;
13131     int i;
13132     int archived;
13133     char *arcDir;
13134
13135     if (! cmailMsgLoaded) {
13136         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13137         return;
13138     }
13139
13140     if (nCmailGames == nCmailResults) {
13141         DisplayError(_("No unfinished games"), 0);
13142         return;
13143     }
13144
13145 #if CMAIL_PROHIBIT_REMAIL
13146     if (cmailMailedMove) {
13147       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);
13148         DisplayError(msg, 0);
13149         return;
13150     }
13151 #endif
13152
13153     if (! (cmailMailedMove || RegisterMove())) return;
13154
13155     if (   cmailMailedMove
13156         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13157       snprintf(string, MSG_SIZ, partCommandString,
13158                appData.debugMode ? " -v" : "", appData.cmailGameName);
13159         commandOutput = popen(string, "r");
13160
13161         if (commandOutput == NULL) {
13162             DisplayError(_("Failed to invoke cmail"), 0);
13163         } else {
13164             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13165                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13166             }
13167             if (nBuffers > 1) {
13168                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13169                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13170                 nBytes = MSG_SIZ - 1;
13171             } else {
13172                 (void) memcpy(msg, buffer, nBytes);
13173             }
13174             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13175
13176             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13177                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13178
13179                 archived = TRUE;
13180                 for (i = 0; i < nCmailGames; i ++) {
13181                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13182                         archived = FALSE;
13183                     }
13184                 }
13185                 if (   archived
13186                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13187                         != NULL)) {
13188                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13189                            arcDir,
13190                            appData.cmailGameName,
13191                            gameInfo.date);
13192                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13193                     cmailMsgLoaded = FALSE;
13194                 }
13195             }
13196
13197             DisplayInformation(msg);
13198             pclose(commandOutput);
13199         }
13200     } else {
13201         if ((*cmailMsg) != '\0') {
13202             DisplayInformation(cmailMsg);
13203         }
13204     }
13205
13206     return;
13207 #endif /* !WIN32 */
13208 }
13209
13210 char *
13211 CmailMsg ()
13212 {
13213 #if WIN32
13214     return NULL;
13215 #else
13216     int  prependComma = 0;
13217     char number[5];
13218     char string[MSG_SIZ];       /* Space for game-list */
13219     int  i;
13220
13221     if (!cmailMsgLoaded) return "";
13222
13223     if (cmailMailedMove) {
13224       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13225     } else {
13226         /* Create a list of games left */
13227       snprintf(string, MSG_SIZ, "[");
13228         for (i = 0; i < nCmailGames; i ++) {
13229             if (! (   cmailMoveRegistered[i]
13230                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13231                 if (prependComma) {
13232                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13233                 } else {
13234                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13235                     prependComma = 1;
13236                 }
13237
13238                 strcat(string, number);
13239             }
13240         }
13241         strcat(string, "]");
13242
13243         if (nCmailMovesRegistered + nCmailResults == 0) {
13244             switch (nCmailGames) {
13245               case 1:
13246                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13247                 break;
13248
13249               case 2:
13250                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13251                 break;
13252
13253               default:
13254                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13255                          nCmailGames);
13256                 break;
13257             }
13258         } else {
13259             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13260               case 1:
13261                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13262                          string);
13263                 break;
13264
13265               case 0:
13266                 if (nCmailResults == nCmailGames) {
13267                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13268                 } else {
13269                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13270                 }
13271                 break;
13272
13273               default:
13274                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13275                          string);
13276             }
13277         }
13278     }
13279     return cmailMsg;
13280 #endif /* WIN32 */
13281 }
13282
13283 void
13284 ResetGameEvent ()
13285 {
13286     if (gameMode == Training)
13287       SetTrainingModeOff();
13288
13289     Reset(TRUE, TRUE);
13290     cmailMsgLoaded = FALSE;
13291     if (appData.icsActive) {
13292       SendToICS(ics_prefix);
13293       SendToICS("refresh\n");
13294     }
13295 }
13296
13297 void
13298 ExitEvent (int status)
13299 {
13300     exiting++;
13301     if (exiting > 2) {
13302       /* Give up on clean exit */
13303       exit(status);
13304     }
13305     if (exiting > 1) {
13306       /* Keep trying for clean exit */
13307       return;
13308     }
13309
13310     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13311
13312     if (telnetISR != NULL) {
13313       RemoveInputSource(telnetISR);
13314     }
13315     if (icsPR != NoProc) {
13316       DestroyChildProcess(icsPR, TRUE);
13317     }
13318
13319     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13320     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13321
13322     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13323     /* make sure this other one finishes before killing it!                  */
13324     if(endingGame) { int count = 0;
13325         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13326         while(endingGame && count++ < 10) DoSleep(1);
13327         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13328     }
13329
13330     /* Kill off chess programs */
13331     if (first.pr != NoProc) {
13332         ExitAnalyzeMode();
13333
13334         DoSleep( appData.delayBeforeQuit );
13335         SendToProgram("quit\n", &first);
13336         DoSleep( appData.delayAfterQuit );
13337         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13338     }
13339     if (second.pr != NoProc) {
13340         DoSleep( appData.delayBeforeQuit );
13341         SendToProgram("quit\n", &second);
13342         DoSleep( appData.delayAfterQuit );
13343         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13344     }
13345     if (first.isr != NULL) {
13346         RemoveInputSource(first.isr);
13347     }
13348     if (second.isr != NULL) {
13349         RemoveInputSource(second.isr);
13350     }
13351
13352     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13353     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13354
13355     ShutDownFrontEnd();
13356     exit(status);
13357 }
13358
13359 void
13360 PauseEvent ()
13361 {
13362     if (appData.debugMode)
13363         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13364     if (pausing) {
13365         pausing = FALSE;
13366         ModeHighlight();
13367         if (gameMode == MachinePlaysWhite ||
13368             gameMode == MachinePlaysBlack) {
13369             StartClocks();
13370         } else {
13371             DisplayBothClocks();
13372         }
13373         if (gameMode == PlayFromGameFile) {
13374             if (appData.timeDelay >= 0)
13375                 AutoPlayGameLoop();
13376         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13377             Reset(FALSE, TRUE);
13378             SendToICS(ics_prefix);
13379             SendToICS("refresh\n");
13380         } else if (currentMove < forwardMostMove) {
13381             ForwardInner(forwardMostMove);
13382         }
13383         pauseExamInvalid = FALSE;
13384     } else {
13385         switch (gameMode) {
13386           default:
13387             return;
13388           case IcsExamining:
13389             pauseExamForwardMostMove = forwardMostMove;
13390             pauseExamInvalid = FALSE;
13391             /* fall through */
13392           case IcsObserving:
13393           case IcsPlayingWhite:
13394           case IcsPlayingBlack:
13395             pausing = TRUE;
13396             ModeHighlight();
13397             return;
13398           case PlayFromGameFile:
13399             (void) StopLoadGameTimer();
13400             pausing = TRUE;
13401             ModeHighlight();
13402             break;
13403           case BeginningOfGame:
13404             if (appData.icsActive) return;
13405             /* else fall through */
13406           case MachinePlaysWhite:
13407           case MachinePlaysBlack:
13408           case TwoMachinesPlay:
13409             if (forwardMostMove == 0)
13410               return;           /* don't pause if no one has moved */
13411             if ((gameMode == MachinePlaysWhite &&
13412                  !WhiteOnMove(forwardMostMove)) ||
13413                 (gameMode == MachinePlaysBlack &&
13414                  WhiteOnMove(forwardMostMove))) {
13415                 StopClocks();
13416             }
13417           case AnalyzeMode:
13418             pausing = TRUE;
13419             ModeHighlight();
13420             break;
13421         }
13422     }
13423 }
13424
13425 void
13426 EditCommentEvent ()
13427 {
13428     char title[MSG_SIZ];
13429
13430     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13431       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13432     } else {
13433       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13434                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13435                parseList[currentMove - 1]);
13436     }
13437
13438     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13439 }
13440
13441
13442 void
13443 EditTagsEvent ()
13444 {
13445     char *tags = PGNTags(&gameInfo);
13446     bookUp = FALSE;
13447     EditTagsPopUp(tags, NULL);
13448     free(tags);
13449 }
13450
13451 void
13452 ToggleSecond ()
13453 {
13454   if(second.analyzing) {
13455     SendToProgram("exit\n", &second);
13456     second.analyzing = FALSE;
13457   } else {
13458     if (second.pr == NoProc) StartChessProgram(&second);
13459     InitChessProgram(&second, FALSE);
13460     FeedMovesToProgram(&second, currentMove);
13461
13462     SendToProgram("analyze\n", &second);
13463     second.analyzing = TRUE;
13464   }
13465 }
13466
13467 /* Toggle ShowThinking */
13468 void
13469 ToggleShowThinking()
13470 {
13471   appData.showThinking = !appData.showThinking;
13472   ShowThinkingEvent();
13473 }
13474
13475 int
13476 AnalyzeModeEvent ()
13477 {
13478     char buf[MSG_SIZ];
13479
13480     if (!first.analysisSupport) {
13481       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13482       DisplayError(buf, 0);
13483       return 0;
13484     }
13485     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13486     if (appData.icsActive) {
13487         if (gameMode != IcsObserving) {
13488           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13489             DisplayError(buf, 0);
13490             /* secure check */
13491             if (appData.icsEngineAnalyze) {
13492                 if (appData.debugMode)
13493                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13494                 ExitAnalyzeMode();
13495                 ModeHighlight();
13496             }
13497             return 0;
13498         }
13499         /* if enable, user wants to disable icsEngineAnalyze */
13500         if (appData.icsEngineAnalyze) {
13501                 ExitAnalyzeMode();
13502                 ModeHighlight();
13503                 return 0;
13504         }
13505         appData.icsEngineAnalyze = TRUE;
13506         if (appData.debugMode)
13507             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13508     }
13509
13510     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13511     if (appData.noChessProgram || gameMode == AnalyzeMode)
13512       return 0;
13513
13514     if (gameMode != AnalyzeFile) {
13515         if (!appData.icsEngineAnalyze) {
13516                EditGameEvent();
13517                if (gameMode != EditGame) return 0;
13518         }
13519         if (!appData.showThinking) ToggleShowThinking();
13520         ResurrectChessProgram();
13521         SendToProgram("analyze\n", &first);
13522         first.analyzing = TRUE;
13523         /*first.maybeThinking = TRUE;*/
13524         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13525         EngineOutputPopUp();
13526     }
13527     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13528     pausing = FALSE;
13529     ModeHighlight();
13530     SetGameInfo();
13531
13532     StartAnalysisClock();
13533     GetTimeMark(&lastNodeCountTime);
13534     lastNodeCount = 0;
13535     return 1;
13536 }
13537
13538 void
13539 AnalyzeFileEvent ()
13540 {
13541     if (appData.noChessProgram || gameMode == AnalyzeFile)
13542       return;
13543
13544     if (!first.analysisSupport) {
13545       char buf[MSG_SIZ];
13546       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13547       DisplayError(buf, 0);
13548       return;
13549     }
13550
13551     if (gameMode != AnalyzeMode) {
13552         EditGameEvent();
13553         if (gameMode != EditGame) return;
13554         if (!appData.showThinking) ToggleShowThinking();
13555         ResurrectChessProgram();
13556         SendToProgram("analyze\n", &first);
13557         first.analyzing = TRUE;
13558         /*first.maybeThinking = TRUE;*/
13559         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13560         EngineOutputPopUp();
13561     }
13562     gameMode = AnalyzeFile;
13563     pausing = FALSE;
13564     ModeHighlight();
13565     SetGameInfo();
13566
13567     StartAnalysisClock();
13568     GetTimeMark(&lastNodeCountTime);
13569     lastNodeCount = 0;
13570     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13571     AnalysisPeriodicEvent(1);
13572 }
13573
13574 void
13575 MachineWhiteEvent ()
13576 {
13577     char buf[MSG_SIZ];
13578     char *bookHit = NULL;
13579
13580     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13581       return;
13582
13583
13584     if (gameMode == PlayFromGameFile ||
13585         gameMode == TwoMachinesPlay  ||
13586         gameMode == Training         ||
13587         gameMode == AnalyzeMode      ||
13588         gameMode == EndOfGame)
13589         EditGameEvent();
13590
13591     if (gameMode == EditPosition)
13592         EditPositionDone(TRUE);
13593
13594     if (!WhiteOnMove(currentMove)) {
13595         DisplayError(_("It is not White's turn"), 0);
13596         return;
13597     }
13598
13599     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13600       ExitAnalyzeMode();
13601
13602     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13603         gameMode == AnalyzeFile)
13604         TruncateGame();
13605
13606     ResurrectChessProgram();    /* in case it isn't running */
13607     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13608         gameMode = MachinePlaysWhite;
13609         ResetClocks();
13610     } else
13611     gameMode = MachinePlaysWhite;
13612     pausing = FALSE;
13613     ModeHighlight();
13614     SetGameInfo();
13615     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13616     DisplayTitle(buf);
13617     if (first.sendName) {
13618       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13619       SendToProgram(buf, &first);
13620     }
13621     if (first.sendTime) {
13622       if (first.useColors) {
13623         SendToProgram("black\n", &first); /*gnu kludge*/
13624       }
13625       SendTimeRemaining(&first, TRUE);
13626     }
13627     if (first.useColors) {
13628       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13629     }
13630     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13631     SetMachineThinkingEnables();
13632     first.maybeThinking = TRUE;
13633     StartClocks();
13634     firstMove = FALSE;
13635
13636     if (appData.autoFlipView && !flipView) {
13637       flipView = !flipView;
13638       DrawPosition(FALSE, NULL);
13639       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13640     }
13641
13642     if(bookHit) { // [HGM] book: simulate book reply
13643         static char bookMove[MSG_SIZ]; // a bit generous?
13644
13645         programStats.nodes = programStats.depth = programStats.time =
13646         programStats.score = programStats.got_only_move = 0;
13647         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13648
13649         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13650         strcat(bookMove, bookHit);
13651         HandleMachineMove(bookMove, &first);
13652     }
13653 }
13654
13655 void
13656 MachineBlackEvent ()
13657 {
13658   char buf[MSG_SIZ];
13659   char *bookHit = NULL;
13660
13661     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13662         return;
13663
13664
13665     if (gameMode == PlayFromGameFile ||
13666         gameMode == TwoMachinesPlay  ||
13667         gameMode == Training         ||
13668         gameMode == AnalyzeMode      ||
13669         gameMode == EndOfGame)
13670         EditGameEvent();
13671
13672     if (gameMode == EditPosition)
13673         EditPositionDone(TRUE);
13674
13675     if (WhiteOnMove(currentMove)) {
13676         DisplayError(_("It is not Black's turn"), 0);
13677         return;
13678     }
13679
13680     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13681       ExitAnalyzeMode();
13682
13683     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13684         gameMode == AnalyzeFile)
13685         TruncateGame();
13686
13687     ResurrectChessProgram();    /* in case it isn't running */
13688     gameMode = MachinePlaysBlack;
13689     pausing = FALSE;
13690     ModeHighlight();
13691     SetGameInfo();
13692     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13693     DisplayTitle(buf);
13694     if (first.sendName) {
13695       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13696       SendToProgram(buf, &first);
13697     }
13698     if (first.sendTime) {
13699       if (first.useColors) {
13700         SendToProgram("white\n", &first); /*gnu kludge*/
13701       }
13702       SendTimeRemaining(&first, FALSE);
13703     }
13704     if (first.useColors) {
13705       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13706     }
13707     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13708     SetMachineThinkingEnables();
13709     first.maybeThinking = TRUE;
13710     StartClocks();
13711
13712     if (appData.autoFlipView && flipView) {
13713       flipView = !flipView;
13714       DrawPosition(FALSE, NULL);
13715       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13716     }
13717     if(bookHit) { // [HGM] book: simulate book reply
13718         static char bookMove[MSG_SIZ]; // a bit generous?
13719
13720         programStats.nodes = programStats.depth = programStats.time =
13721         programStats.score = programStats.got_only_move = 0;
13722         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13723
13724         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13725         strcat(bookMove, bookHit);
13726         HandleMachineMove(bookMove, &first);
13727     }
13728 }
13729
13730
13731 void
13732 DisplayTwoMachinesTitle ()
13733 {
13734     char buf[MSG_SIZ];
13735     if (appData.matchGames > 0) {
13736         if(appData.tourneyFile[0]) {
13737           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13738                    gameInfo.white, _("vs."), gameInfo.black,
13739                    nextGame+1, appData.matchGames+1,
13740                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13741         } else 
13742         if (first.twoMachinesColor[0] == 'w') {
13743           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13744                    gameInfo.white, _("vs."),  gameInfo.black,
13745                    first.matchWins, second.matchWins,
13746                    matchGame - 1 - (first.matchWins + second.matchWins));
13747         } else {
13748           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13749                    gameInfo.white, _("vs."), gameInfo.black,
13750                    second.matchWins, first.matchWins,
13751                    matchGame - 1 - (first.matchWins + second.matchWins));
13752         }
13753     } else {
13754       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13755     }
13756     DisplayTitle(buf);
13757 }
13758
13759 void
13760 SettingsMenuIfReady ()
13761 {
13762   if (second.lastPing != second.lastPong) {
13763     DisplayMessage("", _("Waiting for second chess program"));
13764     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13765     return;
13766   }
13767   ThawUI();
13768   DisplayMessage("", "");
13769   SettingsPopUp(&second);
13770 }
13771
13772 int
13773 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13774 {
13775     char buf[MSG_SIZ];
13776     if (cps->pr == NoProc) {
13777         StartChessProgram(cps);
13778         if (cps->protocolVersion == 1) {
13779           retry();
13780         } else {
13781           /* kludge: allow timeout for initial "feature" command */
13782           FreezeUI();
13783           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13784           DisplayMessage("", buf);
13785           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13786         }
13787         return 1;
13788     }
13789     return 0;
13790 }
13791
13792 void
13793 TwoMachinesEvent P((void))
13794 {
13795     int i;
13796     char buf[MSG_SIZ];
13797     ChessProgramState *onmove;
13798     char *bookHit = NULL;
13799     static int stalling = 0;
13800     TimeMark now;
13801     long wait;
13802
13803     if (appData.noChessProgram) return;
13804
13805     switch (gameMode) {
13806       case TwoMachinesPlay:
13807         return;
13808       case MachinePlaysWhite:
13809       case MachinePlaysBlack:
13810         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13811             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13812             return;
13813         }
13814         /* fall through */
13815       case BeginningOfGame:
13816       case PlayFromGameFile:
13817       case EndOfGame:
13818         EditGameEvent();
13819         if (gameMode != EditGame) return;
13820         break;
13821       case EditPosition:
13822         EditPositionDone(TRUE);
13823         break;
13824       case AnalyzeMode:
13825       case AnalyzeFile:
13826         ExitAnalyzeMode();
13827         break;
13828       case EditGame:
13829       default:
13830         break;
13831     }
13832
13833 //    forwardMostMove = currentMove;
13834     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13835
13836     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13837
13838     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13839     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13840       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13841       return;
13842     }
13843
13844     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13845         DisplayError("second engine does not play this", 0);
13846         return;
13847     }
13848
13849     if(!stalling) {
13850       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13851       SendToProgram("force\n", &second);
13852       stalling = 1;
13853       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13854       return;
13855     }
13856     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13857     if(appData.matchPause>10000 || appData.matchPause<10)
13858                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13859     wait = SubtractTimeMarks(&now, &pauseStart);
13860     if(wait < appData.matchPause) {
13861         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13862         return;
13863     }
13864     // we are now committed to starting the game
13865     stalling = 0;
13866     DisplayMessage("", "");
13867     if (startedFromSetupPosition) {
13868         SendBoard(&second, backwardMostMove);
13869     if (appData.debugMode) {
13870         fprintf(debugFP, "Two Machines\n");
13871     }
13872     }
13873     for (i = backwardMostMove; i < forwardMostMove; i++) {
13874         SendMoveToProgram(i, &second);
13875     }
13876
13877     gameMode = TwoMachinesPlay;
13878     pausing = FALSE;
13879     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13880     SetGameInfo();
13881     DisplayTwoMachinesTitle();
13882     firstMove = TRUE;
13883     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13884         onmove = &first;
13885     } else {
13886         onmove = &second;
13887     }
13888     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13889     SendToProgram(first.computerString, &first);
13890     if (first.sendName) {
13891       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13892       SendToProgram(buf, &first);
13893     }
13894     SendToProgram(second.computerString, &second);
13895     if (second.sendName) {
13896       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13897       SendToProgram(buf, &second);
13898     }
13899
13900     ResetClocks();
13901     if (!first.sendTime || !second.sendTime) {
13902         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13903         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13904     }
13905     if (onmove->sendTime) {
13906       if (onmove->useColors) {
13907         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13908       }
13909       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13910     }
13911     if (onmove->useColors) {
13912       SendToProgram(onmove->twoMachinesColor, onmove);
13913     }
13914     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13915 //    SendToProgram("go\n", onmove);
13916     onmove->maybeThinking = TRUE;
13917     SetMachineThinkingEnables();
13918
13919     StartClocks();
13920
13921     if(bookHit) { // [HGM] book: simulate book reply
13922         static char bookMove[MSG_SIZ]; // a bit generous?
13923
13924         programStats.nodes = programStats.depth = programStats.time =
13925         programStats.score = programStats.got_only_move = 0;
13926         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13927
13928         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13929         strcat(bookMove, bookHit);
13930         savedMessage = bookMove; // args for deferred call
13931         savedState = onmove;
13932         ScheduleDelayedEvent(DeferredBookMove, 1);
13933     }
13934 }
13935
13936 void
13937 TrainingEvent ()
13938 {
13939     if (gameMode == Training) {
13940       SetTrainingModeOff();
13941       gameMode = PlayFromGameFile;
13942       DisplayMessage("", _("Training mode off"));
13943     } else {
13944       gameMode = Training;
13945       animateTraining = appData.animate;
13946
13947       /* make sure we are not already at the end of the game */
13948       if (currentMove < forwardMostMove) {
13949         SetTrainingModeOn();
13950         DisplayMessage("", _("Training mode on"));
13951       } else {
13952         gameMode = PlayFromGameFile;
13953         DisplayError(_("Already at end of game"), 0);
13954       }
13955     }
13956     ModeHighlight();
13957 }
13958
13959 void
13960 IcsClientEvent ()
13961 {
13962     if (!appData.icsActive) return;
13963     switch (gameMode) {
13964       case IcsPlayingWhite:
13965       case IcsPlayingBlack:
13966       case IcsObserving:
13967       case IcsIdle:
13968       case BeginningOfGame:
13969       case IcsExamining:
13970         return;
13971
13972       case EditGame:
13973         break;
13974
13975       case EditPosition:
13976         EditPositionDone(TRUE);
13977         break;
13978
13979       case AnalyzeMode:
13980       case AnalyzeFile:
13981         ExitAnalyzeMode();
13982         break;
13983
13984       default:
13985         EditGameEvent();
13986         break;
13987     }
13988
13989     gameMode = IcsIdle;
13990     ModeHighlight();
13991     return;
13992 }
13993
13994 void
13995 EditGameEvent ()
13996 {
13997     int i;
13998
13999     switch (gameMode) {
14000       case Training:
14001         SetTrainingModeOff();
14002         break;
14003       case MachinePlaysWhite:
14004       case MachinePlaysBlack:
14005       case BeginningOfGame:
14006         SendToProgram("force\n", &first);
14007         SetUserThinkingEnables();
14008         break;
14009       case PlayFromGameFile:
14010         (void) StopLoadGameTimer();
14011         if (gameFileFP != NULL) {
14012             gameFileFP = NULL;
14013         }
14014         break;
14015       case EditPosition:
14016         EditPositionDone(TRUE);
14017         break;
14018       case AnalyzeMode:
14019       case AnalyzeFile:
14020         ExitAnalyzeMode();
14021         SendToProgram("force\n", &first);
14022         break;
14023       case TwoMachinesPlay:
14024         GameEnds(EndOfFile, NULL, GE_PLAYER);
14025         ResurrectChessProgram();
14026         SetUserThinkingEnables();
14027         break;
14028       case EndOfGame:
14029         ResurrectChessProgram();
14030         break;
14031       case IcsPlayingBlack:
14032       case IcsPlayingWhite:
14033         DisplayError(_("Warning: You are still playing a game"), 0);
14034         break;
14035       case IcsObserving:
14036         DisplayError(_("Warning: You are still observing a game"), 0);
14037         break;
14038       case IcsExamining:
14039         DisplayError(_("Warning: You are still examining a game"), 0);
14040         break;
14041       case IcsIdle:
14042         break;
14043       case EditGame:
14044       default:
14045         return;
14046     }
14047
14048     pausing = FALSE;
14049     StopClocks();
14050     first.offeredDraw = second.offeredDraw = 0;
14051
14052     if (gameMode == PlayFromGameFile) {
14053         whiteTimeRemaining = timeRemaining[0][currentMove];
14054         blackTimeRemaining = timeRemaining[1][currentMove];
14055         DisplayTitle("");
14056     }
14057
14058     if (gameMode == MachinePlaysWhite ||
14059         gameMode == MachinePlaysBlack ||
14060         gameMode == TwoMachinesPlay ||
14061         gameMode == EndOfGame) {
14062         i = forwardMostMove;
14063         while (i > currentMove) {
14064             SendToProgram("undo\n", &first);
14065             i--;
14066         }
14067         if(!adjustedClock) {
14068         whiteTimeRemaining = timeRemaining[0][currentMove];
14069         blackTimeRemaining = timeRemaining[1][currentMove];
14070         DisplayBothClocks();
14071         }
14072         if (whiteFlag || blackFlag) {
14073             whiteFlag = blackFlag = 0;
14074         }
14075         DisplayTitle("");
14076     }
14077
14078     gameMode = EditGame;
14079     ModeHighlight();
14080     SetGameInfo();
14081 }
14082
14083
14084 void
14085 EditPositionEvent ()
14086 {
14087     if (gameMode == EditPosition) {
14088         EditGameEvent();
14089         return;
14090     }
14091
14092     EditGameEvent();
14093     if (gameMode != EditGame) return;
14094
14095     gameMode = EditPosition;
14096     ModeHighlight();
14097     SetGameInfo();
14098     if (currentMove > 0)
14099       CopyBoard(boards[0], boards[currentMove]);
14100
14101     blackPlaysFirst = !WhiteOnMove(currentMove);
14102     ResetClocks();
14103     currentMove = forwardMostMove = backwardMostMove = 0;
14104     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14105     DisplayMove(-1);
14106     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14107 }
14108
14109 void
14110 ExitAnalyzeMode ()
14111 {
14112     /* [DM] icsEngineAnalyze - possible call from other functions */
14113     if (appData.icsEngineAnalyze) {
14114         appData.icsEngineAnalyze = FALSE;
14115
14116         DisplayMessage("",_("Close ICS engine analyze..."));
14117     }
14118     if (first.analysisSupport && first.analyzing) {
14119       SendToBoth("exit\n");
14120       first.analyzing = second.analyzing = FALSE;
14121     }
14122     thinkOutput[0] = NULLCHAR;
14123 }
14124
14125 void
14126 EditPositionDone (Boolean fakeRights)
14127 {
14128     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14129
14130     startedFromSetupPosition = TRUE;
14131     InitChessProgram(&first, FALSE);
14132     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14133       boards[0][EP_STATUS] = EP_NONE;
14134       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14135       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14136         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14137         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14138       } else boards[0][CASTLING][2] = NoRights;
14139       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14140         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14141         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14142       } else boards[0][CASTLING][5] = NoRights;
14143       if(gameInfo.variant == VariantSChess) {
14144         int i;
14145         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14146           boards[0][VIRGIN][i] = 0;
14147           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14148           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14149         }
14150       }
14151     }
14152     SendToProgram("force\n", &first);
14153     if (blackPlaysFirst) {
14154         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14155         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14156         currentMove = forwardMostMove = backwardMostMove = 1;
14157         CopyBoard(boards[1], boards[0]);
14158     } else {
14159         currentMove = forwardMostMove = backwardMostMove = 0;
14160     }
14161     SendBoard(&first, forwardMostMove);
14162     if (appData.debugMode) {
14163         fprintf(debugFP, "EditPosDone\n");
14164     }
14165     DisplayTitle("");
14166     DisplayMessage("", "");
14167     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14168     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14169     gameMode = EditGame;
14170     ModeHighlight();
14171     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14172     ClearHighlights(); /* [AS] */
14173 }
14174
14175 /* Pause for `ms' milliseconds */
14176 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14177 void
14178 TimeDelay (long ms)
14179 {
14180     TimeMark m1, m2;
14181
14182     GetTimeMark(&m1);
14183     do {
14184         GetTimeMark(&m2);
14185     } while (SubtractTimeMarks(&m2, &m1) < ms);
14186 }
14187
14188 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14189 void
14190 SendMultiLineToICS (char *buf)
14191 {
14192     char temp[MSG_SIZ+1], *p;
14193     int len;
14194
14195     len = strlen(buf);
14196     if (len > MSG_SIZ)
14197       len = MSG_SIZ;
14198
14199     strncpy(temp, buf, len);
14200     temp[len] = 0;
14201
14202     p = temp;
14203     while (*p) {
14204         if (*p == '\n' || *p == '\r')
14205           *p = ' ';
14206         ++p;
14207     }
14208
14209     strcat(temp, "\n");
14210     SendToICS(temp);
14211     SendToPlayer(temp, strlen(temp));
14212 }
14213
14214 void
14215 SetWhiteToPlayEvent ()
14216 {
14217     if (gameMode == EditPosition) {
14218         blackPlaysFirst = FALSE;
14219         DisplayBothClocks();    /* works because currentMove is 0 */
14220     } else if (gameMode == IcsExamining) {
14221         SendToICS(ics_prefix);
14222         SendToICS("tomove white\n");
14223     }
14224 }
14225
14226 void
14227 SetBlackToPlayEvent ()
14228 {
14229     if (gameMode == EditPosition) {
14230         blackPlaysFirst = TRUE;
14231         currentMove = 1;        /* kludge */
14232         DisplayBothClocks();
14233         currentMove = 0;
14234     } else if (gameMode == IcsExamining) {
14235         SendToICS(ics_prefix);
14236         SendToICS("tomove black\n");
14237     }
14238 }
14239
14240 void
14241 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14242 {
14243     char buf[MSG_SIZ];
14244     ChessSquare piece = boards[0][y][x];
14245
14246     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14247
14248     switch (selection) {
14249       case ClearBoard:
14250         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14251             SendToICS(ics_prefix);
14252             SendToICS("bsetup clear\n");
14253         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14254             SendToICS(ics_prefix);
14255             SendToICS("clearboard\n");
14256         } else {
14257             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14258                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14259                 for (y = 0; y < BOARD_HEIGHT; y++) {
14260                     if (gameMode == IcsExamining) {
14261                         if (boards[currentMove][y][x] != EmptySquare) {
14262                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14263                                     AAA + x, ONE + y);
14264                             SendToICS(buf);
14265                         }
14266                     } else {
14267                         boards[0][y][x] = p;
14268                     }
14269                 }
14270             }
14271         }
14272         if (gameMode == EditPosition) {
14273             DrawPosition(FALSE, boards[0]);
14274         }
14275         break;
14276
14277       case WhitePlay:
14278         SetWhiteToPlayEvent();
14279         break;
14280
14281       case BlackPlay:
14282         SetBlackToPlayEvent();
14283         break;
14284
14285       case EmptySquare:
14286         if (gameMode == IcsExamining) {
14287             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14288             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14289             SendToICS(buf);
14290         } else {
14291             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14292                 if(x == BOARD_LEFT-2) {
14293                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14294                     boards[0][y][1] = 0;
14295                 } else
14296                 if(x == BOARD_RGHT+1) {
14297                     if(y >= gameInfo.holdingsSize) break;
14298                     boards[0][y][BOARD_WIDTH-2] = 0;
14299                 } else break;
14300             }
14301             boards[0][y][x] = EmptySquare;
14302             DrawPosition(FALSE, boards[0]);
14303         }
14304         break;
14305
14306       case PromotePiece:
14307         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14308            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14309             selection = (ChessSquare) (PROMOTED piece);
14310         } else if(piece == EmptySquare) selection = WhiteSilver;
14311         else selection = (ChessSquare)((int)piece - 1);
14312         goto defaultlabel;
14313
14314       case DemotePiece:
14315         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14316            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14317             selection = (ChessSquare) (DEMOTED piece);
14318         } else if(piece == EmptySquare) selection = BlackSilver;
14319         else selection = (ChessSquare)((int)piece + 1);
14320         goto defaultlabel;
14321
14322       case WhiteQueen:
14323       case BlackQueen:
14324         if(gameInfo.variant == VariantShatranj ||
14325            gameInfo.variant == VariantXiangqi  ||
14326            gameInfo.variant == VariantCourier  ||
14327            gameInfo.variant == VariantMakruk     )
14328             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14329         goto defaultlabel;
14330
14331       case WhiteKing:
14332       case BlackKing:
14333         if(gameInfo.variant == VariantXiangqi)
14334             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14335         if(gameInfo.variant == VariantKnightmate)
14336             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14337       default:
14338         defaultlabel:
14339         if (gameMode == IcsExamining) {
14340             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14341             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14342                      PieceToChar(selection), AAA + x, ONE + y);
14343             SendToICS(buf);
14344         } else {
14345             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14346                 int n;
14347                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14348                     n = PieceToNumber(selection - BlackPawn);
14349                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14350                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14351                     boards[0][BOARD_HEIGHT-1-n][1]++;
14352                 } else
14353                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14354                     n = PieceToNumber(selection);
14355                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14356                     boards[0][n][BOARD_WIDTH-1] = selection;
14357                     boards[0][n][BOARD_WIDTH-2]++;
14358                 }
14359             } else
14360             boards[0][y][x] = selection;
14361             DrawPosition(TRUE, boards[0]);
14362             ClearHighlights();
14363             fromX = fromY = -1;
14364         }
14365         break;
14366     }
14367 }
14368
14369
14370 void
14371 DropMenuEvent (ChessSquare selection, int x, int y)
14372 {
14373     ChessMove moveType;
14374
14375     switch (gameMode) {
14376       case IcsPlayingWhite:
14377       case MachinePlaysBlack:
14378         if (!WhiteOnMove(currentMove)) {
14379             DisplayMoveError(_("It is Black's turn"));
14380             return;
14381         }
14382         moveType = WhiteDrop;
14383         break;
14384       case IcsPlayingBlack:
14385       case MachinePlaysWhite:
14386         if (WhiteOnMove(currentMove)) {
14387             DisplayMoveError(_("It is White's turn"));
14388             return;
14389         }
14390         moveType = BlackDrop;
14391         break;
14392       case EditGame:
14393         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14394         break;
14395       default:
14396         return;
14397     }
14398
14399     if (moveType == BlackDrop && selection < BlackPawn) {
14400       selection = (ChessSquare) ((int) selection
14401                                  + (int) BlackPawn - (int) WhitePawn);
14402     }
14403     if (boards[currentMove][y][x] != EmptySquare) {
14404         DisplayMoveError(_("That square is occupied"));
14405         return;
14406     }
14407
14408     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14409 }
14410
14411 void
14412 AcceptEvent ()
14413 {
14414     /* Accept a pending offer of any kind from opponent */
14415
14416     if (appData.icsActive) {
14417         SendToICS(ics_prefix);
14418         SendToICS("accept\n");
14419     } else if (cmailMsgLoaded) {
14420         if (currentMove == cmailOldMove &&
14421             commentList[cmailOldMove] != NULL &&
14422             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14423                    "Black offers a draw" : "White offers a draw")) {
14424             TruncateGame();
14425             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14426             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14427         } else {
14428             DisplayError(_("There is no pending offer on this move"), 0);
14429             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14430         }
14431     } else {
14432         /* Not used for offers from chess program */
14433     }
14434 }
14435
14436 void
14437 DeclineEvent ()
14438 {
14439     /* Decline a pending offer of any kind from opponent */
14440
14441     if (appData.icsActive) {
14442         SendToICS(ics_prefix);
14443         SendToICS("decline\n");
14444     } else if (cmailMsgLoaded) {
14445         if (currentMove == cmailOldMove &&
14446             commentList[cmailOldMove] != NULL &&
14447             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14448                    "Black offers a draw" : "White offers a draw")) {
14449 #ifdef NOTDEF
14450             AppendComment(cmailOldMove, "Draw declined", TRUE);
14451             DisplayComment(cmailOldMove - 1, "Draw declined");
14452 #endif /*NOTDEF*/
14453         } else {
14454             DisplayError(_("There is no pending offer on this move"), 0);
14455         }
14456     } else {
14457         /* Not used for offers from chess program */
14458     }
14459 }
14460
14461 void
14462 RematchEvent ()
14463 {
14464     /* Issue ICS rematch command */
14465     if (appData.icsActive) {
14466         SendToICS(ics_prefix);
14467         SendToICS("rematch\n");
14468     }
14469 }
14470
14471 void
14472 CallFlagEvent ()
14473 {
14474     /* Call your opponent's flag (claim a win on time) */
14475     if (appData.icsActive) {
14476         SendToICS(ics_prefix);
14477         SendToICS("flag\n");
14478     } else {
14479         switch (gameMode) {
14480           default:
14481             return;
14482           case MachinePlaysWhite:
14483             if (whiteFlag) {
14484                 if (blackFlag)
14485                   GameEnds(GameIsDrawn, "Both players ran out of time",
14486                            GE_PLAYER);
14487                 else
14488                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14489             } else {
14490                 DisplayError(_("Your opponent is not out of time"), 0);
14491             }
14492             break;
14493           case MachinePlaysBlack:
14494             if (blackFlag) {
14495                 if (whiteFlag)
14496                   GameEnds(GameIsDrawn, "Both players ran out of time",
14497                            GE_PLAYER);
14498                 else
14499                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14500             } else {
14501                 DisplayError(_("Your opponent is not out of time"), 0);
14502             }
14503             break;
14504         }
14505     }
14506 }
14507
14508 void
14509 ClockClick (int which)
14510 {       // [HGM] code moved to back-end from winboard.c
14511         if(which) { // black clock
14512           if (gameMode == EditPosition || gameMode == IcsExamining) {
14513             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14514             SetBlackToPlayEvent();
14515           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14516           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14517           } else if (shiftKey) {
14518             AdjustClock(which, -1);
14519           } else if (gameMode == IcsPlayingWhite ||
14520                      gameMode == MachinePlaysBlack) {
14521             CallFlagEvent();
14522           }
14523         } else { // white clock
14524           if (gameMode == EditPosition || gameMode == IcsExamining) {
14525             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14526             SetWhiteToPlayEvent();
14527           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14528           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14529           } else if (shiftKey) {
14530             AdjustClock(which, -1);
14531           } else if (gameMode == IcsPlayingBlack ||
14532                    gameMode == MachinePlaysWhite) {
14533             CallFlagEvent();
14534           }
14535         }
14536 }
14537
14538 void
14539 DrawEvent ()
14540 {
14541     /* Offer draw or accept pending draw offer from opponent */
14542
14543     if (appData.icsActive) {
14544         /* Note: tournament rules require draw offers to be
14545            made after you make your move but before you punch
14546            your clock.  Currently ICS doesn't let you do that;
14547            instead, you immediately punch your clock after making
14548            a move, but you can offer a draw at any time. */
14549
14550         SendToICS(ics_prefix);
14551         SendToICS("draw\n");
14552         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14553     } else if (cmailMsgLoaded) {
14554         if (currentMove == cmailOldMove &&
14555             commentList[cmailOldMove] != NULL &&
14556             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14557                    "Black offers a draw" : "White offers a draw")) {
14558             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14559             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14560         } else if (currentMove == cmailOldMove + 1) {
14561             char *offer = WhiteOnMove(cmailOldMove) ?
14562               "White offers a draw" : "Black offers a draw";
14563             AppendComment(currentMove, offer, TRUE);
14564             DisplayComment(currentMove - 1, offer);
14565             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14566         } else {
14567             DisplayError(_("You must make your move before offering a draw"), 0);
14568             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14569         }
14570     } else if (first.offeredDraw) {
14571         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14572     } else {
14573         if (first.sendDrawOffers) {
14574             SendToProgram("draw\n", &first);
14575             userOfferedDraw = TRUE;
14576         }
14577     }
14578 }
14579
14580 void
14581 AdjournEvent ()
14582 {
14583     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14584
14585     if (appData.icsActive) {
14586         SendToICS(ics_prefix);
14587         SendToICS("adjourn\n");
14588     } else {
14589         /* Currently GNU Chess doesn't offer or accept Adjourns */
14590     }
14591 }
14592
14593
14594 void
14595 AbortEvent ()
14596 {
14597     /* Offer Abort or accept pending Abort offer from opponent */
14598
14599     if (appData.icsActive) {
14600         SendToICS(ics_prefix);
14601         SendToICS("abort\n");
14602     } else {
14603         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14604     }
14605 }
14606
14607 void
14608 ResignEvent ()
14609 {
14610     /* Resign.  You can do this even if it's not your turn. */
14611
14612     if (appData.icsActive) {
14613         SendToICS(ics_prefix);
14614         SendToICS("resign\n");
14615     } else {
14616         switch (gameMode) {
14617           case MachinePlaysWhite:
14618             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14619             break;
14620           case MachinePlaysBlack:
14621             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14622             break;
14623           case EditGame:
14624             if (cmailMsgLoaded) {
14625                 TruncateGame();
14626                 if (WhiteOnMove(cmailOldMove)) {
14627                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14628                 } else {
14629                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14630                 }
14631                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14632             }
14633             break;
14634           default:
14635             break;
14636         }
14637     }
14638 }
14639
14640
14641 void
14642 StopObservingEvent ()
14643 {
14644     /* Stop observing current games */
14645     SendToICS(ics_prefix);
14646     SendToICS("unobserve\n");
14647 }
14648
14649 void
14650 StopExaminingEvent ()
14651 {
14652     /* Stop observing current game */
14653     SendToICS(ics_prefix);
14654     SendToICS("unexamine\n");
14655 }
14656
14657 void
14658 ForwardInner (int target)
14659 {
14660     int limit; int oldSeekGraphUp = seekGraphUp;
14661
14662     if (appData.debugMode)
14663         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14664                 target, currentMove, forwardMostMove);
14665
14666     if (gameMode == EditPosition)
14667       return;
14668
14669     seekGraphUp = FALSE;
14670     MarkTargetSquares(1);
14671
14672     if (gameMode == PlayFromGameFile && !pausing)
14673       PauseEvent();
14674
14675     if (gameMode == IcsExamining && pausing)
14676       limit = pauseExamForwardMostMove;
14677     else
14678       limit = forwardMostMove;
14679
14680     if (target > limit) target = limit;
14681
14682     if (target > 0 && moveList[target - 1][0]) {
14683         int fromX, fromY, toX, toY;
14684         toX = moveList[target - 1][2] - AAA;
14685         toY = moveList[target - 1][3] - ONE;
14686         if (moveList[target - 1][1] == '@') {
14687             if (appData.highlightLastMove) {
14688                 SetHighlights(-1, -1, toX, toY);
14689             }
14690         } else {
14691             fromX = moveList[target - 1][0] - AAA;
14692             fromY = moveList[target - 1][1] - ONE;
14693             if (target == currentMove + 1) {
14694                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14695             }
14696             if (appData.highlightLastMove) {
14697                 SetHighlights(fromX, fromY, toX, toY);
14698             }
14699         }
14700     }
14701     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14702         gameMode == Training || gameMode == PlayFromGameFile ||
14703         gameMode == AnalyzeFile) {
14704         while (currentMove < target) {
14705             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14706             SendMoveToProgram(currentMove++, &first);
14707         }
14708     } else {
14709         currentMove = target;
14710     }
14711
14712     if (gameMode == EditGame || gameMode == EndOfGame) {
14713         whiteTimeRemaining = timeRemaining[0][currentMove];
14714         blackTimeRemaining = timeRemaining[1][currentMove];
14715     }
14716     DisplayBothClocks();
14717     DisplayMove(currentMove - 1);
14718     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14719     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14720     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14721         DisplayComment(currentMove - 1, commentList[currentMove]);
14722     }
14723     ClearMap(); // [HGM] exclude: invalidate map
14724 }
14725
14726
14727 void
14728 ForwardEvent ()
14729 {
14730     if (gameMode == IcsExamining && !pausing) {
14731         SendToICS(ics_prefix);
14732         SendToICS("forward\n");
14733     } else {
14734         ForwardInner(currentMove + 1);
14735     }
14736 }
14737
14738 void
14739 ToEndEvent ()
14740 {
14741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14742         /* to optimze, we temporarily turn off analysis mode while we feed
14743          * the remaining moves to the engine. Otherwise we get analysis output
14744          * after each move.
14745          */
14746         if (first.analysisSupport) {
14747           SendToProgram("exit\nforce\n", &first);
14748           first.analyzing = FALSE;
14749         }
14750     }
14751
14752     if (gameMode == IcsExamining && !pausing) {
14753         SendToICS(ics_prefix);
14754         SendToICS("forward 999999\n");
14755     } else {
14756         ForwardInner(forwardMostMove);
14757     }
14758
14759     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14760         /* we have fed all the moves, so reactivate analysis mode */
14761         SendToProgram("analyze\n", &first);
14762         first.analyzing = TRUE;
14763         /*first.maybeThinking = TRUE;*/
14764         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14765     }
14766 }
14767
14768 void
14769 BackwardInner (int target)
14770 {
14771     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14772
14773     if (appData.debugMode)
14774         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14775                 target, currentMove, forwardMostMove);
14776
14777     if (gameMode == EditPosition) return;
14778     seekGraphUp = FALSE;
14779     MarkTargetSquares(1);
14780     if (currentMove <= backwardMostMove) {
14781         ClearHighlights();
14782         DrawPosition(full_redraw, boards[currentMove]);
14783         return;
14784     }
14785     if (gameMode == PlayFromGameFile && !pausing)
14786       PauseEvent();
14787
14788     if (moveList[target][0]) {
14789         int fromX, fromY, toX, toY;
14790         toX = moveList[target][2] - AAA;
14791         toY = moveList[target][3] - ONE;
14792         if (moveList[target][1] == '@') {
14793             if (appData.highlightLastMove) {
14794                 SetHighlights(-1, -1, toX, toY);
14795             }
14796         } else {
14797             fromX = moveList[target][0] - AAA;
14798             fromY = moveList[target][1] - ONE;
14799             if (target == currentMove - 1) {
14800                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14801             }
14802             if (appData.highlightLastMove) {
14803                 SetHighlights(fromX, fromY, toX, toY);
14804             }
14805         }
14806     }
14807     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14808         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14809         while (currentMove > target) {
14810             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14811                 // null move cannot be undone. Reload program with move history before it.
14812                 int i;
14813                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14814                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14815                 }
14816                 SendBoard(&first, i); 
14817               if(second.analyzing) SendBoard(&second, i);
14818                 for(currentMove=i; currentMove<target; currentMove++) {
14819                     SendMoveToProgram(currentMove, &first);
14820                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14821                 }
14822                 break;
14823             }
14824             SendToBoth("undo\n");
14825             currentMove--;
14826         }
14827     } else {
14828         currentMove = target;
14829     }
14830
14831     if (gameMode == EditGame || gameMode == EndOfGame) {
14832         whiteTimeRemaining = timeRemaining[0][currentMove];
14833         blackTimeRemaining = timeRemaining[1][currentMove];
14834     }
14835     DisplayBothClocks();
14836     DisplayMove(currentMove - 1);
14837     DrawPosition(full_redraw, boards[currentMove]);
14838     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14839     // [HGM] PV info: routine tests if comment empty
14840     DisplayComment(currentMove - 1, commentList[currentMove]);
14841     ClearMap(); // [HGM] exclude: invalidate map
14842 }
14843
14844 void
14845 BackwardEvent ()
14846 {
14847     if (gameMode == IcsExamining && !pausing) {
14848         SendToICS(ics_prefix);
14849         SendToICS("backward\n");
14850     } else {
14851         BackwardInner(currentMove - 1);
14852     }
14853 }
14854
14855 void
14856 ToStartEvent ()
14857 {
14858     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14859         /* to optimize, we temporarily turn off analysis mode while we undo
14860          * all the moves. Otherwise we get analysis output after each undo.
14861          */
14862         if (first.analysisSupport) {
14863           SendToProgram("exit\nforce\n", &first);
14864           first.analyzing = FALSE;
14865         }
14866     }
14867
14868     if (gameMode == IcsExamining && !pausing) {
14869         SendToICS(ics_prefix);
14870         SendToICS("backward 999999\n");
14871     } else {
14872         BackwardInner(backwardMostMove);
14873     }
14874
14875     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14876         /* we have fed all the moves, so reactivate analysis mode */
14877         SendToProgram("analyze\n", &first);
14878         first.analyzing = TRUE;
14879         /*first.maybeThinking = TRUE;*/
14880         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14881     }
14882 }
14883
14884 void
14885 ToNrEvent (int to)
14886 {
14887   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14888   if (to >= forwardMostMove) to = forwardMostMove;
14889   if (to <= backwardMostMove) to = backwardMostMove;
14890   if (to < currentMove) {
14891     BackwardInner(to);
14892   } else {
14893     ForwardInner(to);
14894   }
14895 }
14896
14897 void
14898 RevertEvent (Boolean annotate)
14899 {
14900     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14901         return;
14902     }
14903     if (gameMode != IcsExamining) {
14904         DisplayError(_("You are not examining a game"), 0);
14905         return;
14906     }
14907     if (pausing) {
14908         DisplayError(_("You can't revert while pausing"), 0);
14909         return;
14910     }
14911     SendToICS(ics_prefix);
14912     SendToICS("revert\n");
14913 }
14914
14915 void
14916 RetractMoveEvent ()
14917 {
14918     switch (gameMode) {
14919       case MachinePlaysWhite:
14920       case MachinePlaysBlack:
14921         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14922             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14923             return;
14924         }
14925         if (forwardMostMove < 2) return;
14926         currentMove = forwardMostMove = forwardMostMove - 2;
14927         whiteTimeRemaining = timeRemaining[0][currentMove];
14928         blackTimeRemaining = timeRemaining[1][currentMove];
14929         DisplayBothClocks();
14930         DisplayMove(currentMove - 1);
14931         ClearHighlights();/*!! could figure this out*/
14932         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14933         SendToProgram("remove\n", &first);
14934         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14935         break;
14936
14937       case BeginningOfGame:
14938       default:
14939         break;
14940
14941       case IcsPlayingWhite:
14942       case IcsPlayingBlack:
14943         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14944             SendToICS(ics_prefix);
14945             SendToICS("takeback 2\n");
14946         } else {
14947             SendToICS(ics_prefix);
14948             SendToICS("takeback 1\n");
14949         }
14950         break;
14951     }
14952 }
14953
14954 void
14955 MoveNowEvent ()
14956 {
14957     ChessProgramState *cps;
14958
14959     switch (gameMode) {
14960       case MachinePlaysWhite:
14961         if (!WhiteOnMove(forwardMostMove)) {
14962             DisplayError(_("It is your turn"), 0);
14963             return;
14964         }
14965         cps = &first;
14966         break;
14967       case MachinePlaysBlack:
14968         if (WhiteOnMove(forwardMostMove)) {
14969             DisplayError(_("It is your turn"), 0);
14970             return;
14971         }
14972         cps = &first;
14973         break;
14974       case TwoMachinesPlay:
14975         if (WhiteOnMove(forwardMostMove) ==
14976             (first.twoMachinesColor[0] == 'w')) {
14977             cps = &first;
14978         } else {
14979             cps = &second;
14980         }
14981         break;
14982       case BeginningOfGame:
14983       default:
14984         return;
14985     }
14986     SendToProgram("?\n", cps);
14987 }
14988
14989 void
14990 TruncateGameEvent ()
14991 {
14992     EditGameEvent();
14993     if (gameMode != EditGame) return;
14994     TruncateGame();
14995 }
14996
14997 void
14998 TruncateGame ()
14999 {
15000     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15001     if (forwardMostMove > currentMove) {
15002         if (gameInfo.resultDetails != NULL) {
15003             free(gameInfo.resultDetails);
15004             gameInfo.resultDetails = NULL;
15005             gameInfo.result = GameUnfinished;
15006         }
15007         forwardMostMove = currentMove;
15008         HistorySet(parseList, backwardMostMove, forwardMostMove,
15009                    currentMove-1);
15010     }
15011 }
15012
15013 void
15014 HintEvent ()
15015 {
15016     if (appData.noChessProgram) return;
15017     switch (gameMode) {
15018       case MachinePlaysWhite:
15019         if (WhiteOnMove(forwardMostMove)) {
15020             DisplayError(_("Wait until your turn"), 0);
15021             return;
15022         }
15023         break;
15024       case BeginningOfGame:
15025       case MachinePlaysBlack:
15026         if (!WhiteOnMove(forwardMostMove)) {
15027             DisplayError(_("Wait until your turn"), 0);
15028             return;
15029         }
15030         break;
15031       default:
15032         DisplayError(_("No hint available"), 0);
15033         return;
15034     }
15035     SendToProgram("hint\n", &first);
15036     hintRequested = TRUE;
15037 }
15038
15039 void
15040 BookEvent ()
15041 {
15042     if (appData.noChessProgram) return;
15043     switch (gameMode) {
15044       case MachinePlaysWhite:
15045         if (WhiteOnMove(forwardMostMove)) {
15046             DisplayError(_("Wait until your turn"), 0);
15047             return;
15048         }
15049         break;
15050       case BeginningOfGame:
15051       case MachinePlaysBlack:
15052         if (!WhiteOnMove(forwardMostMove)) {
15053             DisplayError(_("Wait until your turn"), 0);
15054             return;
15055         }
15056         break;
15057       case EditPosition:
15058         EditPositionDone(TRUE);
15059         break;
15060       case TwoMachinesPlay:
15061         return;
15062       default:
15063         break;
15064     }
15065     SendToProgram("bk\n", &first);
15066     bookOutput[0] = NULLCHAR;
15067     bookRequested = TRUE;
15068 }
15069
15070 void
15071 AboutGameEvent ()
15072 {
15073     char *tags = PGNTags(&gameInfo);
15074     TagsPopUp(tags, CmailMsg());
15075     free(tags);
15076 }
15077
15078 /* end button procedures */
15079
15080 void
15081 PrintPosition (FILE *fp, int move)
15082 {
15083     int i, j;
15084
15085     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15086         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15087             char c = PieceToChar(boards[move][i][j]);
15088             fputc(c == 'x' ? '.' : c, fp);
15089             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15090         }
15091     }
15092     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15093       fprintf(fp, "white to play\n");
15094     else
15095       fprintf(fp, "black to play\n");
15096 }
15097
15098 void
15099 PrintOpponents (FILE *fp)
15100 {
15101     if (gameInfo.white != NULL) {
15102         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15103     } else {
15104         fprintf(fp, "\n");
15105     }
15106 }
15107
15108 /* Find last component of program's own name, using some heuristics */
15109 void
15110 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15111 {
15112     char *p, *q, c;
15113     int local = (strcmp(host, "localhost") == 0);
15114     while (!local && (p = strchr(prog, ';')) != NULL) {
15115         p++;
15116         while (*p == ' ') p++;
15117         prog = p;
15118     }
15119     if (*prog == '"' || *prog == '\'') {
15120         q = strchr(prog + 1, *prog);
15121     } else {
15122         q = strchr(prog, ' ');
15123     }
15124     if (q == NULL) q = prog + strlen(prog);
15125     p = q;
15126     while (p >= prog && *p != '/' && *p != '\\') p--;
15127     p++;
15128     if(p == prog && *p == '"') p++;
15129     c = *q; *q = 0;
15130     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15131     memcpy(buf, p, q - p);
15132     buf[q - p] = NULLCHAR;
15133     if (!local) {
15134         strcat(buf, "@");
15135         strcat(buf, host);
15136     }
15137 }
15138
15139 char *
15140 TimeControlTagValue ()
15141 {
15142     char buf[MSG_SIZ];
15143     if (!appData.clockMode) {
15144       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15145     } else if (movesPerSession > 0) {
15146       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15147     } else if (timeIncrement == 0) {
15148       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15149     } else {
15150       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15151     }
15152     return StrSave(buf);
15153 }
15154
15155 void
15156 SetGameInfo ()
15157 {
15158     /* This routine is used only for certain modes */
15159     VariantClass v = gameInfo.variant;
15160     ChessMove r = GameUnfinished;
15161     char *p = NULL;
15162
15163     if(keepInfo) return;
15164
15165     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15166         r = gameInfo.result;
15167         p = gameInfo.resultDetails;
15168         gameInfo.resultDetails = NULL;
15169     }
15170     ClearGameInfo(&gameInfo);
15171     gameInfo.variant = v;
15172
15173     switch (gameMode) {
15174       case MachinePlaysWhite:
15175         gameInfo.event = StrSave( appData.pgnEventHeader );
15176         gameInfo.site = StrSave(HostName());
15177         gameInfo.date = PGNDate();
15178         gameInfo.round = StrSave("-");
15179         gameInfo.white = StrSave(first.tidy);
15180         gameInfo.black = StrSave(UserName());
15181         gameInfo.timeControl = TimeControlTagValue();
15182         break;
15183
15184       case MachinePlaysBlack:
15185         gameInfo.event = StrSave( appData.pgnEventHeader );
15186         gameInfo.site = StrSave(HostName());
15187         gameInfo.date = PGNDate();
15188         gameInfo.round = StrSave("-");
15189         gameInfo.white = StrSave(UserName());
15190         gameInfo.black = StrSave(first.tidy);
15191         gameInfo.timeControl = TimeControlTagValue();
15192         break;
15193
15194       case TwoMachinesPlay:
15195         gameInfo.event = StrSave( appData.pgnEventHeader );
15196         gameInfo.site = StrSave(HostName());
15197         gameInfo.date = PGNDate();
15198         if (roundNr > 0) {
15199             char buf[MSG_SIZ];
15200             snprintf(buf, MSG_SIZ, "%d", roundNr);
15201             gameInfo.round = StrSave(buf);
15202         } else {
15203             gameInfo.round = StrSave("-");
15204         }
15205         if (first.twoMachinesColor[0] == 'w') {
15206             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15207             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15208         } else {
15209             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15210             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15211         }
15212         gameInfo.timeControl = TimeControlTagValue();
15213         break;
15214
15215       case EditGame:
15216         gameInfo.event = StrSave("Edited game");
15217         gameInfo.site = StrSave(HostName());
15218         gameInfo.date = PGNDate();
15219         gameInfo.round = StrSave("-");
15220         gameInfo.white = StrSave("-");
15221         gameInfo.black = StrSave("-");
15222         gameInfo.result = r;
15223         gameInfo.resultDetails = p;
15224         break;
15225
15226       case EditPosition:
15227         gameInfo.event = StrSave("Edited position");
15228         gameInfo.site = StrSave(HostName());
15229         gameInfo.date = PGNDate();
15230         gameInfo.round = StrSave("-");
15231         gameInfo.white = StrSave("-");
15232         gameInfo.black = StrSave("-");
15233         break;
15234
15235       case IcsPlayingWhite:
15236       case IcsPlayingBlack:
15237       case IcsObserving:
15238       case IcsExamining:
15239         break;
15240
15241       case PlayFromGameFile:
15242         gameInfo.event = StrSave("Game from non-PGN file");
15243         gameInfo.site = StrSave(HostName());
15244         gameInfo.date = PGNDate();
15245         gameInfo.round = StrSave("-");
15246         gameInfo.white = StrSave("?");
15247         gameInfo.black = StrSave("?");
15248         break;
15249
15250       default:
15251         break;
15252     }
15253 }
15254
15255 void
15256 ReplaceComment (int index, char *text)
15257 {
15258     int len;
15259     char *p;
15260     float score;
15261
15262     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15263        pvInfoList[index-1].depth == len &&
15264        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15265        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15266     while (*text == '\n') text++;
15267     len = strlen(text);
15268     while (len > 0 && text[len - 1] == '\n') len--;
15269
15270     if (commentList[index] != NULL)
15271       free(commentList[index]);
15272
15273     if (len == 0) {
15274         commentList[index] = NULL;
15275         return;
15276     }
15277   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15278       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15279       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15280     commentList[index] = (char *) malloc(len + 2);
15281     strncpy(commentList[index], text, len);
15282     commentList[index][len] = '\n';
15283     commentList[index][len + 1] = NULLCHAR;
15284   } else {
15285     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15286     char *p;
15287     commentList[index] = (char *) malloc(len + 7);
15288     safeStrCpy(commentList[index], "{\n", 3);
15289     safeStrCpy(commentList[index]+2, text, len+1);
15290     commentList[index][len+2] = NULLCHAR;
15291     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15292     strcat(commentList[index], "\n}\n");
15293   }
15294 }
15295
15296 void
15297 CrushCRs (char *text)
15298 {
15299   char *p = text;
15300   char *q = text;
15301   char ch;
15302
15303   do {
15304     ch = *p++;
15305     if (ch == '\r') continue;
15306     *q++ = ch;
15307   } while (ch != '\0');
15308 }
15309
15310 void
15311 AppendComment (int index, char *text, Boolean addBraces)
15312 /* addBraces  tells if we should add {} */
15313 {
15314     int oldlen, len;
15315     char *old;
15316
15317 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15318     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15319
15320     CrushCRs(text);
15321     while (*text == '\n') text++;
15322     len = strlen(text);
15323     while (len > 0 && text[len - 1] == '\n') len--;
15324     text[len] = NULLCHAR;
15325
15326     if (len == 0) return;
15327
15328     if (commentList[index] != NULL) {
15329       Boolean addClosingBrace = addBraces;
15330         old = commentList[index];
15331         oldlen = strlen(old);
15332         while(commentList[index][oldlen-1] ==  '\n')
15333           commentList[index][--oldlen] = NULLCHAR;
15334         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15335         safeStrCpy(commentList[index], old, oldlen + len + 6);
15336         free(old);
15337         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15338         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15339           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15340           while (*text == '\n') { text++; len--; }
15341           commentList[index][--oldlen] = NULLCHAR;
15342       }
15343         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15344         else          strcat(commentList[index], "\n");
15345         strcat(commentList[index], text);
15346         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15347         else          strcat(commentList[index], "\n");
15348     } else {
15349         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15350         if(addBraces)
15351           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15352         else commentList[index][0] = NULLCHAR;
15353         strcat(commentList[index], text);
15354         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15355         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15356     }
15357 }
15358
15359 static char *
15360 FindStr (char * text, char * sub_text)
15361 {
15362     char * result = strstr( text, sub_text );
15363
15364     if( result != NULL ) {
15365         result += strlen( sub_text );
15366     }
15367
15368     return result;
15369 }
15370
15371 /* [AS] Try to extract PV info from PGN comment */
15372 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15373 char *
15374 GetInfoFromComment (int index, char * text)
15375 {
15376     char * sep = text, *p;
15377
15378     if( text != NULL && index > 0 ) {
15379         int score = 0;
15380         int depth = 0;
15381         int time = -1, sec = 0, deci;
15382         char * s_eval = FindStr( text, "[%eval " );
15383         char * s_emt = FindStr( text, "[%emt " );
15384
15385         if( s_eval != NULL || s_emt != NULL ) {
15386             /* New style */
15387             char delim;
15388
15389             if( s_eval != NULL ) {
15390                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15391                     return text;
15392                 }
15393
15394                 if( delim != ']' ) {
15395                     return text;
15396                 }
15397             }
15398
15399             if( s_emt != NULL ) {
15400             }
15401                 return text;
15402         }
15403         else {
15404             /* We expect something like: [+|-]nnn.nn/dd */
15405             int score_lo = 0;
15406
15407             if(*text != '{') return text; // [HGM] braces: must be normal comment
15408
15409             sep = strchr( text, '/' );
15410             if( sep == NULL || sep < (text+4) ) {
15411                 return text;
15412             }
15413
15414             p = text;
15415             if(p[1] == '(') { // comment starts with PV
15416                p = strchr(p, ')'); // locate end of PV
15417                if(p == NULL || sep < p+5) return text;
15418                // at this point we have something like "{(.*) +0.23/6 ..."
15419                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15420                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15421                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15422             }
15423             time = -1; sec = -1; deci = -1;
15424             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15425                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15426                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15427                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15428                 return text;
15429             }
15430
15431             if( score_lo < 0 || score_lo >= 100 ) {
15432                 return text;
15433             }
15434
15435             if(sec >= 0) time = 600*time + 10*sec; else
15436             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15437
15438             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15439
15440             /* [HGM] PV time: now locate end of PV info */
15441             while( *++sep >= '0' && *sep <= '9'); // strip depth
15442             if(time >= 0)
15443             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15444             if(sec >= 0)
15445             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15446             if(deci >= 0)
15447             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15448             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15449         }
15450
15451         if( depth <= 0 ) {
15452             return text;
15453         }
15454
15455         if( time < 0 ) {
15456             time = -1;
15457         }
15458
15459         pvInfoList[index-1].depth = depth;
15460         pvInfoList[index-1].score = score;
15461         pvInfoList[index-1].time  = 10*time; // centi-sec
15462         if(*sep == '}') *sep = 0; else *--sep = '{';
15463         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15464     }
15465     return sep;
15466 }
15467
15468 void
15469 SendToProgram (char *message, ChessProgramState *cps)
15470 {
15471     int count, outCount, error;
15472     char buf[MSG_SIZ];
15473
15474     if (cps->pr == NoProc) return;
15475     Attention(cps);
15476
15477     if (appData.debugMode) {
15478         TimeMark now;
15479         GetTimeMark(&now);
15480         fprintf(debugFP, "%ld >%-6s: %s",
15481                 SubtractTimeMarks(&now, &programStartTime),
15482                 cps->which, message);
15483         if(serverFP)
15484             fprintf(serverFP, "%ld >%-6s: %s",
15485                 SubtractTimeMarks(&now, &programStartTime),
15486                 cps->which, message), fflush(serverFP);
15487     }
15488
15489     count = strlen(message);
15490     outCount = OutputToProcess(cps->pr, message, count, &error);
15491     if (outCount < count && !exiting
15492                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15493       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15494       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15495         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15496             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15497                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15498                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15499                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15500             } else {
15501                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15502                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15503                 gameInfo.result = res;
15504             }
15505             gameInfo.resultDetails = StrSave(buf);
15506         }
15507         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15508         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15509     }
15510 }
15511
15512 void
15513 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15514 {
15515     char *end_str;
15516     char buf[MSG_SIZ];
15517     ChessProgramState *cps = (ChessProgramState *)closure;
15518
15519     if (isr != cps->isr) return; /* Killed intentionally */
15520     if (count <= 0) {
15521         if (count == 0) {
15522             RemoveInputSource(cps->isr);
15523             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15524                     _(cps->which), cps->program);
15525             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15526             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15527                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15528                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15529                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15530                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15531                 } else {
15532                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15533                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15534                     gameInfo.result = res;
15535                 }
15536                 gameInfo.resultDetails = StrSave(buf);
15537             }
15538             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15539             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15540         } else {
15541             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15542                     _(cps->which), cps->program);
15543             RemoveInputSource(cps->isr);
15544
15545             /* [AS] Program is misbehaving badly... kill it */
15546             if( count == -2 ) {
15547                 DestroyChildProcess( cps->pr, 9 );
15548                 cps->pr = NoProc;
15549             }
15550
15551             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15552         }
15553         return;
15554     }
15555
15556     if ((end_str = strchr(message, '\r')) != NULL)
15557       *end_str = NULLCHAR;
15558     if ((end_str = strchr(message, '\n')) != NULL)
15559       *end_str = NULLCHAR;
15560
15561     if (appData.debugMode) {
15562         TimeMark now; int print = 1;
15563         char *quote = ""; char c; int i;
15564
15565         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15566                 char start = message[0];
15567                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15568                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15569                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15570                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15571                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15572                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15573                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15574                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15575                    sscanf(message, "hint: %c", &c)!=1 && 
15576                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15577                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15578                     print = (appData.engineComments >= 2);
15579                 }
15580                 message[0] = start; // restore original message
15581         }
15582         if(print) {
15583                 GetTimeMark(&now);
15584                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15585                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15586                         quote,
15587                         message);
15588                 if(serverFP)
15589                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15590                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15591                         quote,
15592                         message), fflush(serverFP);
15593         }
15594     }
15595
15596     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15597     if (appData.icsEngineAnalyze) {
15598         if (strstr(message, "whisper") != NULL ||
15599              strstr(message, "kibitz") != NULL ||
15600             strstr(message, "tellics") != NULL) return;
15601     }
15602
15603     HandleMachineMove(message, cps);
15604 }
15605
15606
15607 void
15608 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15609 {
15610     char buf[MSG_SIZ];
15611     int seconds;
15612
15613     if( timeControl_2 > 0 ) {
15614         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15615             tc = timeControl_2;
15616         }
15617     }
15618     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15619     inc /= cps->timeOdds;
15620     st  /= cps->timeOdds;
15621
15622     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15623
15624     if (st > 0) {
15625       /* Set exact time per move, normally using st command */
15626       if (cps->stKludge) {
15627         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15628         seconds = st % 60;
15629         if (seconds == 0) {
15630           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15631         } else {
15632           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15633         }
15634       } else {
15635         snprintf(buf, MSG_SIZ, "st %d\n", st);
15636       }
15637     } else {
15638       /* Set conventional or incremental time control, using level command */
15639       if (seconds == 0) {
15640         /* Note old gnuchess bug -- minutes:seconds used to not work.
15641            Fixed in later versions, but still avoid :seconds
15642            when seconds is 0. */
15643         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15644       } else {
15645         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15646                  seconds, inc/1000.);
15647       }
15648     }
15649     SendToProgram(buf, cps);
15650
15651     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15652     /* Orthogonally, limit search to given depth */
15653     if (sd > 0) {
15654       if (cps->sdKludge) {
15655         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15656       } else {
15657         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15658       }
15659       SendToProgram(buf, cps);
15660     }
15661
15662     if(cps->nps >= 0) { /* [HGM] nps */
15663         if(cps->supportsNPS == FALSE)
15664           cps->nps = -1; // don't use if engine explicitly says not supported!
15665         else {
15666           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15667           SendToProgram(buf, cps);
15668         }
15669     }
15670 }
15671
15672 ChessProgramState *
15673 WhitePlayer ()
15674 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15675 {
15676     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15677        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15678         return &second;
15679     return &first;
15680 }
15681
15682 void
15683 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15684 {
15685     char message[MSG_SIZ];
15686     long time, otime;
15687
15688     /* Note: this routine must be called when the clocks are stopped
15689        or when they have *just* been set or switched; otherwise
15690        it will be off by the time since the current tick started.
15691     */
15692     if (machineWhite) {
15693         time = whiteTimeRemaining / 10;
15694         otime = blackTimeRemaining / 10;
15695     } else {
15696         time = blackTimeRemaining / 10;
15697         otime = whiteTimeRemaining / 10;
15698     }
15699     /* [HGM] translate opponent's time by time-odds factor */
15700     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15701
15702     if (time <= 0) time = 1;
15703     if (otime <= 0) otime = 1;
15704
15705     snprintf(message, MSG_SIZ, "time %ld\n", time);
15706     SendToProgram(message, cps);
15707
15708     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15709     SendToProgram(message, cps);
15710 }
15711
15712 int
15713 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15714 {
15715   char buf[MSG_SIZ];
15716   int len = strlen(name);
15717   int val;
15718
15719   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15720     (*p) += len + 1;
15721     sscanf(*p, "%d", &val);
15722     *loc = (val != 0);
15723     while (**p && **p != ' ')
15724       (*p)++;
15725     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15726     SendToProgram(buf, cps);
15727     return TRUE;
15728   }
15729   return FALSE;
15730 }
15731
15732 int
15733 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15734 {
15735   char buf[MSG_SIZ];
15736   int len = strlen(name);
15737   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15738     (*p) += len + 1;
15739     sscanf(*p, "%d", loc);
15740     while (**p && **p != ' ') (*p)++;
15741     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15742     SendToProgram(buf, cps);
15743     return TRUE;
15744   }
15745   return FALSE;
15746 }
15747
15748 int
15749 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15750 {
15751   char buf[MSG_SIZ];
15752   int len = strlen(name);
15753   if (strncmp((*p), name, len) == 0
15754       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15755     (*p) += len + 2;
15756     sscanf(*p, "%[^\"]", loc);
15757     while (**p && **p != '\"') (*p)++;
15758     if (**p == '\"') (*p)++;
15759     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15760     SendToProgram(buf, cps);
15761     return TRUE;
15762   }
15763   return FALSE;
15764 }
15765
15766 int
15767 ParseOption (Option *opt, ChessProgramState *cps)
15768 // [HGM] options: process the string that defines an engine option, and determine
15769 // name, type, default value, and allowed value range
15770 {
15771         char *p, *q, buf[MSG_SIZ];
15772         int n, min = (-1)<<31, max = 1<<31, def;
15773
15774         if(p = strstr(opt->name, " -spin ")) {
15775             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15776             if(max < min) max = min; // enforce consistency
15777             if(def < min) def = min;
15778             if(def > max) def = max;
15779             opt->value = def;
15780             opt->min = min;
15781             opt->max = max;
15782             opt->type = Spin;
15783         } else if((p = strstr(opt->name, " -slider "))) {
15784             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15785             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15786             if(max < min) max = min; // enforce consistency
15787             if(def < min) def = min;
15788             if(def > max) def = max;
15789             opt->value = def;
15790             opt->min = min;
15791             opt->max = max;
15792             opt->type = Spin; // Slider;
15793         } else if((p = strstr(opt->name, " -string "))) {
15794             opt->textValue = p+9;
15795             opt->type = TextBox;
15796         } else if((p = strstr(opt->name, " -file "))) {
15797             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15798             opt->textValue = p+7;
15799             opt->type = FileName; // FileName;
15800         } else if((p = strstr(opt->name, " -path "))) {
15801             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15802             opt->textValue = p+7;
15803             opt->type = PathName; // PathName;
15804         } else if(p = strstr(opt->name, " -check ")) {
15805             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15806             opt->value = (def != 0);
15807             opt->type = CheckBox;
15808         } else if(p = strstr(opt->name, " -combo ")) {
15809             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15810             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15811             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15812             opt->value = n = 0;
15813             while(q = StrStr(q, " /// ")) {
15814                 n++; *q = 0;    // count choices, and null-terminate each of them
15815                 q += 5;
15816                 if(*q == '*') { // remember default, which is marked with * prefix
15817                     q++;
15818                     opt->value = n;
15819                 }
15820                 cps->comboList[cps->comboCnt++] = q;
15821             }
15822             cps->comboList[cps->comboCnt++] = NULL;
15823             opt->max = n + 1;
15824             opt->type = ComboBox;
15825         } else if(p = strstr(opt->name, " -button")) {
15826             opt->type = Button;
15827         } else if(p = strstr(opt->name, " -save")) {
15828             opt->type = SaveButton;
15829         } else return FALSE;
15830         *p = 0; // terminate option name
15831         // now look if the command-line options define a setting for this engine option.
15832         if(cps->optionSettings && cps->optionSettings[0])
15833             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15834         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15835           snprintf(buf, MSG_SIZ, "option %s", p);
15836                 if(p = strstr(buf, ",")) *p = 0;
15837                 if(q = strchr(buf, '=')) switch(opt->type) {
15838                     case ComboBox:
15839                         for(n=0; n<opt->max; n++)
15840                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15841                         break;
15842                     case TextBox:
15843                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15844                         break;
15845                     case Spin:
15846                     case CheckBox:
15847                         opt->value = atoi(q+1);
15848                     default:
15849                         break;
15850                 }
15851                 strcat(buf, "\n");
15852                 SendToProgram(buf, cps);
15853         }
15854         return TRUE;
15855 }
15856
15857 void
15858 FeatureDone (ChessProgramState *cps, int val)
15859 {
15860   DelayedEventCallback cb = GetDelayedEvent();
15861   if ((cb == InitBackEnd3 && cps == &first) ||
15862       (cb == SettingsMenuIfReady && cps == &second) ||
15863       (cb == LoadEngine) ||
15864       (cb == TwoMachinesEventIfReady)) {
15865     CancelDelayedEvent();
15866     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15867   }
15868   cps->initDone = val;
15869 }
15870
15871 /* Parse feature command from engine */
15872 void
15873 ParseFeatures (char *args, ChessProgramState *cps)
15874 {
15875   char *p = args;
15876   char *q;
15877   int val;
15878   char buf[MSG_SIZ];
15879
15880   for (;;) {
15881     while (*p == ' ') p++;
15882     if (*p == NULLCHAR) return;
15883
15884     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15885     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15886     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15887     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15888     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15889     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15890     if (BoolFeature(&p, "reuse", &val, cps)) {
15891       /* Engine can disable reuse, but can't enable it if user said no */
15892       if (!val) cps->reuse = FALSE;
15893       continue;
15894     }
15895     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15896     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15897       if (gameMode == TwoMachinesPlay) {
15898         DisplayTwoMachinesTitle();
15899       } else {
15900         DisplayTitle("");
15901       }
15902       continue;
15903     }
15904     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15905     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15906     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15907     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15908     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15909     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15910     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15911     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15912     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15913     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15914     if (IntFeature(&p, "done", &val, cps)) {
15915       FeatureDone(cps, val);
15916       continue;
15917     }
15918     /* Added by Tord: */
15919     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15920     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15921     /* End of additions by Tord */
15922
15923     /* [HGM] added features: */
15924     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15925     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15926     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15927     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15928     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15929     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15930     if (StringFeature(&p, "option", buf, cps)) {
15931         FREE(cps->option[cps->nrOptions].name);
15932         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15933         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15934         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15935           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15936             SendToProgram(buf, cps);
15937             continue;
15938         }
15939         if(cps->nrOptions >= MAX_OPTIONS) {
15940             cps->nrOptions--;
15941             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15942             DisplayError(buf, 0);
15943         }
15944         continue;
15945     }
15946     /* End of additions by HGM */
15947
15948     /* unknown feature: complain and skip */
15949     q = p;
15950     while (*q && *q != '=') q++;
15951     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15952     SendToProgram(buf, cps);
15953     p = q;
15954     if (*p == '=') {
15955       p++;
15956       if (*p == '\"') {
15957         p++;
15958         while (*p && *p != '\"') p++;
15959         if (*p == '\"') p++;
15960       } else {
15961         while (*p && *p != ' ') p++;
15962       }
15963     }
15964   }
15965
15966 }
15967
15968 void
15969 PeriodicUpdatesEvent (int newState)
15970 {
15971     if (newState == appData.periodicUpdates)
15972       return;
15973
15974     appData.periodicUpdates=newState;
15975
15976     /* Display type changes, so update it now */
15977 //    DisplayAnalysis();
15978
15979     /* Get the ball rolling again... */
15980     if (newState) {
15981         AnalysisPeriodicEvent(1);
15982         StartAnalysisClock();
15983     }
15984 }
15985
15986 void
15987 PonderNextMoveEvent (int newState)
15988 {
15989     if (newState == appData.ponderNextMove) return;
15990     if (gameMode == EditPosition) EditPositionDone(TRUE);
15991     if (newState) {
15992         SendToProgram("hard\n", &first);
15993         if (gameMode == TwoMachinesPlay) {
15994             SendToProgram("hard\n", &second);
15995         }
15996     } else {
15997         SendToProgram("easy\n", &first);
15998         thinkOutput[0] = NULLCHAR;
15999         if (gameMode == TwoMachinesPlay) {
16000             SendToProgram("easy\n", &second);
16001         }
16002     }
16003     appData.ponderNextMove = newState;
16004 }
16005
16006 void
16007 NewSettingEvent (int option, int *feature, char *command, int value)
16008 {
16009     char buf[MSG_SIZ];
16010
16011     if (gameMode == EditPosition) EditPositionDone(TRUE);
16012     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16013     if(feature == NULL || *feature) SendToProgram(buf, &first);
16014     if (gameMode == TwoMachinesPlay) {
16015         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16016     }
16017 }
16018
16019 void
16020 ShowThinkingEvent ()
16021 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16022 {
16023     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16024     int newState = appData.showThinking
16025         // [HGM] thinking: other features now need thinking output as well
16026         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16027
16028     if (oldState == newState) return;
16029     oldState = newState;
16030     if (gameMode == EditPosition) EditPositionDone(TRUE);
16031     if (oldState) {
16032         SendToProgram("post\n", &first);
16033         if (gameMode == TwoMachinesPlay) {
16034             SendToProgram("post\n", &second);
16035         }
16036     } else {
16037         SendToProgram("nopost\n", &first);
16038         thinkOutput[0] = NULLCHAR;
16039         if (gameMode == TwoMachinesPlay) {
16040             SendToProgram("nopost\n", &second);
16041         }
16042     }
16043 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16044 }
16045
16046 void
16047 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16048 {
16049   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16050   if (pr == NoProc) return;
16051   AskQuestion(title, question, replyPrefix, pr);
16052 }
16053
16054 void
16055 TypeInEvent (char firstChar)
16056 {
16057     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
16058         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16059         gameMode == AnalyzeMode || gameMode == EditGame || 
16060         gameMode == EditPosition || gameMode == IcsExamining ||
16061         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16062         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16063                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16064                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16065         gameMode == Training) PopUpMoveDialog(firstChar);
16066 }
16067
16068 void
16069 TypeInDoneEvent (char *move)
16070 {
16071         Board board;
16072         int n, fromX, fromY, toX, toY;
16073         char promoChar;
16074         ChessMove moveType;
16075
16076         // [HGM] FENedit
16077         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16078                 EditPositionPasteFEN(move);
16079                 return;
16080         }
16081         // [HGM] movenum: allow move number to be typed in any mode
16082         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16083           ToNrEvent(2*n-1);
16084           return;
16085         }
16086         // undocumented kludge: allow command-line option to be typed in!
16087         // (potentially fatal, and does not implement the effect of the option.)
16088         // should only be used for options that are values on which future decisions will be made,
16089         // and definitely not on options that would be used during initialization.
16090         if(strstr(move, "!!! -") == move) {
16091             ParseArgsFromString(move+4);
16092             return;
16093         }
16094
16095       if (gameMode != EditGame && currentMove != forwardMostMove && 
16096         gameMode != Training) {
16097         DisplayMoveError(_("Displayed move is not current"));
16098       } else {
16099         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16100           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16101         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16102         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16103           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16104           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16105         } else {
16106           DisplayMoveError(_("Could not parse move"));
16107         }
16108       }
16109 }
16110
16111 void
16112 DisplayMove (int moveNumber)
16113 {
16114     char message[MSG_SIZ];
16115     char res[MSG_SIZ];
16116     char cpThinkOutput[MSG_SIZ];
16117
16118     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16119
16120     if (moveNumber == forwardMostMove - 1 ||
16121         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16122
16123         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16124
16125         if (strchr(cpThinkOutput, '\n')) {
16126             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16127         }
16128     } else {
16129         *cpThinkOutput = NULLCHAR;
16130     }
16131
16132     /* [AS] Hide thinking from human user */
16133     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16134         *cpThinkOutput = NULLCHAR;
16135         if( thinkOutput[0] != NULLCHAR ) {
16136             int i;
16137
16138             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16139                 cpThinkOutput[i] = '.';
16140             }
16141             cpThinkOutput[i] = NULLCHAR;
16142             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16143         }
16144     }
16145
16146     if (moveNumber == forwardMostMove - 1 &&
16147         gameInfo.resultDetails != NULL) {
16148         if (gameInfo.resultDetails[0] == NULLCHAR) {
16149           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16150         } else {
16151           snprintf(res, MSG_SIZ, " {%s} %s",
16152                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16153         }
16154     } else {
16155         res[0] = NULLCHAR;
16156     }
16157
16158     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16159         DisplayMessage(res, cpThinkOutput);
16160     } else {
16161       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16162                 WhiteOnMove(moveNumber) ? " " : ".. ",
16163                 parseList[moveNumber], res);
16164         DisplayMessage(message, cpThinkOutput);
16165     }
16166 }
16167
16168 void
16169 DisplayComment (int moveNumber, char *text)
16170 {
16171     char title[MSG_SIZ];
16172
16173     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16174       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16175     } else {
16176       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16177               WhiteOnMove(moveNumber) ? " " : ".. ",
16178               parseList[moveNumber]);
16179     }
16180     if (text != NULL && (appData.autoDisplayComment || commentUp))
16181         CommentPopUp(title, text);
16182 }
16183
16184 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16185  * might be busy thinking or pondering.  It can be omitted if your
16186  * gnuchess is configured to stop thinking immediately on any user
16187  * input.  However, that gnuchess feature depends on the FIONREAD
16188  * ioctl, which does not work properly on some flavors of Unix.
16189  */
16190 void
16191 Attention (ChessProgramState *cps)
16192 {
16193 #if ATTENTION
16194     if (!cps->useSigint) return;
16195     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16196     switch (gameMode) {
16197       case MachinePlaysWhite:
16198       case MachinePlaysBlack:
16199       case TwoMachinesPlay:
16200       case IcsPlayingWhite:
16201       case IcsPlayingBlack:
16202       case AnalyzeMode:
16203       case AnalyzeFile:
16204         /* Skip if we know it isn't thinking */
16205         if (!cps->maybeThinking) return;
16206         if (appData.debugMode)
16207           fprintf(debugFP, "Interrupting %s\n", cps->which);
16208         InterruptChildProcess(cps->pr);
16209         cps->maybeThinking = FALSE;
16210         break;
16211       default:
16212         break;
16213     }
16214 #endif /*ATTENTION*/
16215 }
16216
16217 int
16218 CheckFlags ()
16219 {
16220     if (whiteTimeRemaining <= 0) {
16221         if (!whiteFlag) {
16222             whiteFlag = TRUE;
16223             if (appData.icsActive) {
16224                 if (appData.autoCallFlag &&
16225                     gameMode == IcsPlayingBlack && !blackFlag) {
16226                   SendToICS(ics_prefix);
16227                   SendToICS("flag\n");
16228                 }
16229             } else {
16230                 if (blackFlag) {
16231                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16232                 } else {
16233                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16234                     if (appData.autoCallFlag) {
16235                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16236                         return TRUE;
16237                     }
16238                 }
16239             }
16240         }
16241     }
16242     if (blackTimeRemaining <= 0) {
16243         if (!blackFlag) {
16244             blackFlag = TRUE;
16245             if (appData.icsActive) {
16246                 if (appData.autoCallFlag &&
16247                     gameMode == IcsPlayingWhite && !whiteFlag) {
16248                   SendToICS(ics_prefix);
16249                   SendToICS("flag\n");
16250                 }
16251             } else {
16252                 if (whiteFlag) {
16253                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16254                 } else {
16255                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16256                     if (appData.autoCallFlag) {
16257                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16258                         return TRUE;
16259                     }
16260                 }
16261             }
16262         }
16263     }
16264     return FALSE;
16265 }
16266
16267 void
16268 CheckTimeControl ()
16269 {
16270     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16271         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16272
16273     /*
16274      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16275      */
16276     if ( !WhiteOnMove(forwardMostMove) ) {
16277         /* White made time control */
16278         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16279         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16280         /* [HGM] time odds: correct new time quota for time odds! */
16281                                             / WhitePlayer()->timeOdds;
16282         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16283     } else {
16284         lastBlack -= blackTimeRemaining;
16285         /* Black made time control */
16286         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16287                                             / WhitePlayer()->other->timeOdds;
16288         lastWhite = whiteTimeRemaining;
16289     }
16290 }
16291
16292 void
16293 DisplayBothClocks ()
16294 {
16295     int wom = gameMode == EditPosition ?
16296       !blackPlaysFirst : WhiteOnMove(currentMove);
16297     DisplayWhiteClock(whiteTimeRemaining, wom);
16298     DisplayBlackClock(blackTimeRemaining, !wom);
16299 }
16300
16301
16302 /* Timekeeping seems to be a portability nightmare.  I think everyone
16303    has ftime(), but I'm really not sure, so I'm including some ifdefs
16304    to use other calls if you don't.  Clocks will be less accurate if
16305    you have neither ftime nor gettimeofday.
16306 */
16307
16308 /* VS 2008 requires the #include outside of the function */
16309 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16310 #include <sys/timeb.h>
16311 #endif
16312
16313 /* Get the current time as a TimeMark */
16314 void
16315 GetTimeMark (TimeMark *tm)
16316 {
16317 #if HAVE_GETTIMEOFDAY
16318
16319     struct timeval timeVal;
16320     struct timezone timeZone;
16321
16322     gettimeofday(&timeVal, &timeZone);
16323     tm->sec = (long) timeVal.tv_sec;
16324     tm->ms = (int) (timeVal.tv_usec / 1000L);
16325
16326 #else /*!HAVE_GETTIMEOFDAY*/
16327 #if HAVE_FTIME
16328
16329 // include <sys/timeb.h> / moved to just above start of function
16330     struct timeb timeB;
16331
16332     ftime(&timeB);
16333     tm->sec = (long) timeB.time;
16334     tm->ms = (int) timeB.millitm;
16335
16336 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16337     tm->sec = (long) time(NULL);
16338     tm->ms = 0;
16339 #endif
16340 #endif
16341 }
16342
16343 /* Return the difference in milliseconds between two
16344    time marks.  We assume the difference will fit in a long!
16345 */
16346 long
16347 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16348 {
16349     return 1000L*(tm2->sec - tm1->sec) +
16350            (long) (tm2->ms - tm1->ms);
16351 }
16352
16353
16354 /*
16355  * Code to manage the game clocks.
16356  *
16357  * In tournament play, black starts the clock and then white makes a move.
16358  * We give the human user a slight advantage if he is playing white---the
16359  * clocks don't run until he makes his first move, so it takes zero time.
16360  * Also, we don't account for network lag, so we could get out of sync
16361  * with GNU Chess's clock -- but then, referees are always right.
16362  */
16363
16364 static TimeMark tickStartTM;
16365 static long intendedTickLength;
16366
16367 long
16368 NextTickLength (long timeRemaining)
16369 {
16370     long nominalTickLength, nextTickLength;
16371
16372     if (timeRemaining > 0L && timeRemaining <= 10000L)
16373       nominalTickLength = 100L;
16374     else
16375       nominalTickLength = 1000L;
16376     nextTickLength = timeRemaining % nominalTickLength;
16377     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16378
16379     return nextTickLength;
16380 }
16381
16382 /* Adjust clock one minute up or down */
16383 void
16384 AdjustClock (Boolean which, int dir)
16385 {
16386     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16387     if(which) blackTimeRemaining += 60000*dir;
16388     else      whiteTimeRemaining += 60000*dir;
16389     DisplayBothClocks();
16390     adjustedClock = TRUE;
16391 }
16392
16393 /* Stop clocks and reset to a fresh time control */
16394 void
16395 ResetClocks ()
16396 {
16397     (void) StopClockTimer();
16398     if (appData.icsActive) {
16399         whiteTimeRemaining = blackTimeRemaining = 0;
16400     } else if (searchTime) {
16401         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16402         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16403     } else { /* [HGM] correct new time quote for time odds */
16404         whiteTC = blackTC = fullTimeControlString;
16405         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16406         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16407     }
16408     if (whiteFlag || blackFlag) {
16409         DisplayTitle("");
16410         whiteFlag = blackFlag = FALSE;
16411     }
16412     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16413     DisplayBothClocks();
16414     adjustedClock = FALSE;
16415 }
16416
16417 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16418
16419 /* Decrement running clock by amount of time that has passed */
16420 void
16421 DecrementClocks ()
16422 {
16423     long timeRemaining;
16424     long lastTickLength, fudge;
16425     TimeMark now;
16426
16427     if (!appData.clockMode) return;
16428     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16429
16430     GetTimeMark(&now);
16431
16432     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16433
16434     /* Fudge if we woke up a little too soon */
16435     fudge = intendedTickLength - lastTickLength;
16436     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16437
16438     if (WhiteOnMove(forwardMostMove)) {
16439         if(whiteNPS >= 0) lastTickLength = 0;
16440         timeRemaining = whiteTimeRemaining -= lastTickLength;
16441         if(timeRemaining < 0 && !appData.icsActive) {
16442             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16443             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16444                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16445                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16446             }
16447         }
16448         DisplayWhiteClock(whiteTimeRemaining - fudge,
16449                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16450     } else {
16451         if(blackNPS >= 0) lastTickLength = 0;
16452         timeRemaining = blackTimeRemaining -= lastTickLength;
16453         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16454             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16455             if(suddenDeath) {
16456                 blackStartMove = forwardMostMove;
16457                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16458             }
16459         }
16460         DisplayBlackClock(blackTimeRemaining - fudge,
16461                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16462     }
16463     if (CheckFlags()) return;
16464
16465     if(twoBoards) { // count down secondary board's clocks as well
16466         activePartnerTime -= lastTickLength;
16467         partnerUp = 1;
16468         if(activePartner == 'W')
16469             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16470         else
16471             DisplayBlackClock(activePartnerTime, TRUE);
16472         partnerUp = 0;
16473     }
16474
16475     tickStartTM = now;
16476     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16477     StartClockTimer(intendedTickLength);
16478
16479     /* if the time remaining has fallen below the alarm threshold, sound the
16480      * alarm. if the alarm has sounded and (due to a takeback or time control
16481      * with increment) the time remaining has increased to a level above the
16482      * threshold, reset the alarm so it can sound again.
16483      */
16484
16485     if (appData.icsActive && appData.icsAlarm) {
16486
16487         /* make sure we are dealing with the user's clock */
16488         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16489                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16490            )) return;
16491
16492         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16493             alarmSounded = FALSE;
16494         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16495             PlayAlarmSound();
16496             alarmSounded = TRUE;
16497         }
16498     }
16499 }
16500
16501
16502 /* A player has just moved, so stop the previously running
16503    clock and (if in clock mode) start the other one.
16504    We redisplay both clocks in case we're in ICS mode, because
16505    ICS gives us an update to both clocks after every move.
16506    Note that this routine is called *after* forwardMostMove
16507    is updated, so the last fractional tick must be subtracted
16508    from the color that is *not* on move now.
16509 */
16510 void
16511 SwitchClocks (int newMoveNr)
16512 {
16513     long lastTickLength;
16514     TimeMark now;
16515     int flagged = FALSE;
16516
16517     GetTimeMark(&now);
16518
16519     if (StopClockTimer() && appData.clockMode) {
16520         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16521         if (!WhiteOnMove(forwardMostMove)) {
16522             if(blackNPS >= 0) lastTickLength = 0;
16523             blackTimeRemaining -= lastTickLength;
16524            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16525 //         if(pvInfoList[forwardMostMove].time == -1)
16526                  pvInfoList[forwardMostMove].time =               // use GUI time
16527                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16528         } else {
16529            if(whiteNPS >= 0) lastTickLength = 0;
16530            whiteTimeRemaining -= lastTickLength;
16531            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16532 //         if(pvInfoList[forwardMostMove].time == -1)
16533                  pvInfoList[forwardMostMove].time =
16534                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16535         }
16536         flagged = CheckFlags();
16537     }
16538     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16539     CheckTimeControl();
16540
16541     if (flagged || !appData.clockMode) return;
16542
16543     switch (gameMode) {
16544       case MachinePlaysBlack:
16545       case MachinePlaysWhite:
16546       case BeginningOfGame:
16547         if (pausing) return;
16548         break;
16549
16550       case EditGame:
16551       case PlayFromGameFile:
16552       case IcsExamining:
16553         return;
16554
16555       default:
16556         break;
16557     }
16558
16559     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16560         if(WhiteOnMove(forwardMostMove))
16561              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16562         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16563     }
16564
16565     tickStartTM = now;
16566     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16567       whiteTimeRemaining : blackTimeRemaining);
16568     StartClockTimer(intendedTickLength);
16569 }
16570
16571
16572 /* Stop both clocks */
16573 void
16574 StopClocks ()
16575 {
16576     long lastTickLength;
16577     TimeMark now;
16578
16579     if (!StopClockTimer()) return;
16580     if (!appData.clockMode) return;
16581
16582     GetTimeMark(&now);
16583
16584     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16585     if (WhiteOnMove(forwardMostMove)) {
16586         if(whiteNPS >= 0) lastTickLength = 0;
16587         whiteTimeRemaining -= lastTickLength;
16588         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16589     } else {
16590         if(blackNPS >= 0) lastTickLength = 0;
16591         blackTimeRemaining -= lastTickLength;
16592         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16593     }
16594     CheckFlags();
16595 }
16596
16597 /* Start clock of player on move.  Time may have been reset, so
16598    if clock is already running, stop and restart it. */
16599 void
16600 StartClocks ()
16601 {
16602     (void) StopClockTimer(); /* in case it was running already */
16603     DisplayBothClocks();
16604     if (CheckFlags()) return;
16605
16606     if (!appData.clockMode) return;
16607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16608
16609     GetTimeMark(&tickStartTM);
16610     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16611       whiteTimeRemaining : blackTimeRemaining);
16612
16613    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16614     whiteNPS = blackNPS = -1;
16615     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16616        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16617         whiteNPS = first.nps;
16618     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16619        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16620         blackNPS = first.nps;
16621     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16622         whiteNPS = second.nps;
16623     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16624         blackNPS = second.nps;
16625     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16626
16627     StartClockTimer(intendedTickLength);
16628 }
16629
16630 char *
16631 TimeString (long ms)
16632 {
16633     long second, minute, hour, day;
16634     char *sign = "";
16635     static char buf[32];
16636
16637     if (ms > 0 && ms <= 9900) {
16638       /* convert milliseconds to tenths, rounding up */
16639       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16640
16641       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16642       return buf;
16643     }
16644
16645     /* convert milliseconds to seconds, rounding up */
16646     /* use floating point to avoid strangeness of integer division
16647        with negative dividends on many machines */
16648     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16649
16650     if (second < 0) {
16651         sign = "-";
16652         second = -second;
16653     }
16654
16655     day = second / (60 * 60 * 24);
16656     second = second % (60 * 60 * 24);
16657     hour = second / (60 * 60);
16658     second = second % (60 * 60);
16659     minute = second / 60;
16660     second = second % 60;
16661
16662     if (day > 0)
16663       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16664               sign, day, hour, minute, second);
16665     else if (hour > 0)
16666       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16667     else
16668       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16669
16670     return buf;
16671 }
16672
16673
16674 /*
16675  * This is necessary because some C libraries aren't ANSI C compliant yet.
16676  */
16677 char *
16678 StrStr (char *string, char *match)
16679 {
16680     int i, length;
16681
16682     length = strlen(match);
16683
16684     for (i = strlen(string) - length; i >= 0; i--, string++)
16685       if (!strncmp(match, string, length))
16686         return string;
16687
16688     return NULL;
16689 }
16690
16691 char *
16692 StrCaseStr (char *string, char *match)
16693 {
16694     int i, j, length;
16695
16696     length = strlen(match);
16697
16698     for (i = strlen(string) - length; i >= 0; i--, string++) {
16699         for (j = 0; j < length; j++) {
16700             if (ToLower(match[j]) != ToLower(string[j]))
16701               break;
16702         }
16703         if (j == length) return string;
16704     }
16705
16706     return NULL;
16707 }
16708
16709 #ifndef _amigados
16710 int
16711 StrCaseCmp (char *s1, char *s2)
16712 {
16713     char c1, c2;
16714
16715     for (;;) {
16716         c1 = ToLower(*s1++);
16717         c2 = ToLower(*s2++);
16718         if (c1 > c2) return 1;
16719         if (c1 < c2) return -1;
16720         if (c1 == NULLCHAR) return 0;
16721     }
16722 }
16723
16724
16725 int
16726 ToLower (int c)
16727 {
16728     return isupper(c) ? tolower(c) : c;
16729 }
16730
16731
16732 int
16733 ToUpper (int c)
16734 {
16735     return islower(c) ? toupper(c) : c;
16736 }
16737 #endif /* !_amigados    */
16738
16739 char *
16740 StrSave (char *s)
16741 {
16742   char *ret;
16743
16744   if ((ret = (char *) malloc(strlen(s) + 1)))
16745     {
16746       safeStrCpy(ret, s, strlen(s)+1);
16747     }
16748   return ret;
16749 }
16750
16751 char *
16752 StrSavePtr (char *s, char **savePtr)
16753 {
16754     if (*savePtr) {
16755         free(*savePtr);
16756     }
16757     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16758       safeStrCpy(*savePtr, s, strlen(s)+1);
16759     }
16760     return(*savePtr);
16761 }
16762
16763 char *
16764 PGNDate ()
16765 {
16766     time_t clock;
16767     struct tm *tm;
16768     char buf[MSG_SIZ];
16769
16770     clock = time((time_t *)NULL);
16771     tm = localtime(&clock);
16772     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16773             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16774     return StrSave(buf);
16775 }
16776
16777
16778 char *
16779 PositionToFEN (int move, char *overrideCastling)
16780 {
16781     int i, j, fromX, fromY, toX, toY;
16782     int whiteToPlay;
16783     char buf[MSG_SIZ];
16784     char *p, *q;
16785     int emptycount;
16786     ChessSquare piece;
16787
16788     whiteToPlay = (gameMode == EditPosition) ?
16789       !blackPlaysFirst : (move % 2 == 0);
16790     p = buf;
16791
16792     /* Piece placement data */
16793     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16794         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16795         emptycount = 0;
16796         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16797             if (boards[move][i][j] == EmptySquare) {
16798                 emptycount++;
16799             } else { ChessSquare piece = boards[move][i][j];
16800                 if (emptycount > 0) {
16801                     if(emptycount<10) /* [HGM] can be >= 10 */
16802                         *p++ = '0' + emptycount;
16803                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16804                     emptycount = 0;
16805                 }
16806                 if(PieceToChar(piece) == '+') {
16807                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16808                     *p++ = '+';
16809                     piece = (ChessSquare)(DEMOTED piece);
16810                 }
16811                 *p++ = PieceToChar(piece);
16812                 if(p[-1] == '~') {
16813                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16814                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16815                     *p++ = '~';
16816                 }
16817             }
16818         }
16819         if (emptycount > 0) {
16820             if(emptycount<10) /* [HGM] can be >= 10 */
16821                 *p++ = '0' + emptycount;
16822             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16823             emptycount = 0;
16824         }
16825         *p++ = '/';
16826     }
16827     *(p - 1) = ' ';
16828
16829     /* [HGM] print Crazyhouse or Shogi holdings */
16830     if( gameInfo.holdingsWidth ) {
16831         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16832         q = p;
16833         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16834             piece = boards[move][i][BOARD_WIDTH-1];
16835             if( piece != EmptySquare )
16836               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16837                   *p++ = PieceToChar(piece);
16838         }
16839         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16840             piece = boards[move][BOARD_HEIGHT-i-1][0];
16841             if( piece != EmptySquare )
16842               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16843                   *p++ = PieceToChar(piece);
16844         }
16845
16846         if( q == p ) *p++ = '-';
16847         *p++ = ']';
16848         *p++ = ' ';
16849     }
16850
16851     /* Active color */
16852     *p++ = whiteToPlay ? 'w' : 'b';
16853     *p++ = ' ';
16854
16855   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16856     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16857   } else {
16858   if(nrCastlingRights) {
16859      q = p;
16860      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16861        /* [HGM] write directly from rights */
16862            if(boards[move][CASTLING][2] != NoRights &&
16863               boards[move][CASTLING][0] != NoRights   )
16864                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16865            if(boards[move][CASTLING][2] != NoRights &&
16866               boards[move][CASTLING][1] != NoRights   )
16867                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16868            if(boards[move][CASTLING][5] != NoRights &&
16869               boards[move][CASTLING][3] != NoRights   )
16870                 *p++ = boards[move][CASTLING][3] + AAA;
16871            if(boards[move][CASTLING][5] != NoRights &&
16872               boards[move][CASTLING][4] != NoRights   )
16873                 *p++ = boards[move][CASTLING][4] + AAA;
16874      } else {
16875
16876         /* [HGM] write true castling rights */
16877         if( nrCastlingRights == 6 ) {
16878             int q, k=0;
16879             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16880                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16881             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16882                  boards[move][CASTLING][2] != NoRights  );
16883             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16884                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16885                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16886                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16887                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16888             }
16889             if(q) *p++ = 'Q';
16890             k = 0;
16891             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16892                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16893             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16894                  boards[move][CASTLING][5] != NoRights  );
16895             if(gameInfo.variant == VariantSChess) {
16896                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16897                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16898                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16899                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16900             }
16901             if(q) *p++ = 'q';
16902         }
16903      }
16904      if (q == p) *p++ = '-'; /* No castling rights */
16905      *p++ = ' ';
16906   }
16907
16908   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16909      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16910     /* En passant target square */
16911     if (move > backwardMostMove) {
16912         fromX = moveList[move - 1][0] - AAA;
16913         fromY = moveList[move - 1][1] - ONE;
16914         toX = moveList[move - 1][2] - AAA;
16915         toY = moveList[move - 1][3] - ONE;
16916         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16917             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16918             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16919             fromX == toX) {
16920             /* 2-square pawn move just happened */
16921             *p++ = toX + AAA;
16922             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16923         } else {
16924             *p++ = '-';
16925         }
16926     } else if(move == backwardMostMove) {
16927         // [HGM] perhaps we should always do it like this, and forget the above?
16928         if((signed char)boards[move][EP_STATUS] >= 0) {
16929             *p++ = boards[move][EP_STATUS] + AAA;
16930             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16931         } else {
16932             *p++ = '-';
16933         }
16934     } else {
16935         *p++ = '-';
16936     }
16937     *p++ = ' ';
16938   }
16939   }
16940
16941     /* [HGM] find reversible plies */
16942     {   int i = 0, j=move;
16943
16944         if (appData.debugMode) { int k;
16945             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16946             for(k=backwardMostMove; k<=forwardMostMove; k++)
16947                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16948
16949         }
16950
16951         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16952         if( j == backwardMostMove ) i += initialRulePlies;
16953         sprintf(p, "%d ", i);
16954         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16955     }
16956     /* Fullmove number */
16957     sprintf(p, "%d", (move / 2) + 1);
16958
16959     return StrSave(buf);
16960 }
16961
16962 Boolean
16963 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16964 {
16965     int i, j;
16966     char *p, c;
16967     int emptycount, virgin[BOARD_FILES];
16968     ChessSquare piece;
16969
16970     p = fen;
16971
16972     /* [HGM] by default clear Crazyhouse holdings, if present */
16973     if(gameInfo.holdingsWidth) {
16974        for(i=0; i<BOARD_HEIGHT; i++) {
16975            board[i][0]             = EmptySquare; /* black holdings */
16976            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16977            board[i][1]             = (ChessSquare) 0; /* black counts */
16978            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16979        }
16980     }
16981
16982     /* Piece placement data */
16983     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16984         j = 0;
16985         for (;;) {
16986             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16987                 if (*p == '/') p++;
16988                 emptycount = gameInfo.boardWidth - j;
16989                 while (emptycount--)
16990                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16991                 break;
16992 #if(BOARD_FILES >= 10)
16993             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16994                 p++; emptycount=10;
16995                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16996                 while (emptycount--)
16997                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16998 #endif
16999             } else if (isdigit(*p)) {
17000                 emptycount = *p++ - '0';
17001                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17002                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17003                 while (emptycount--)
17004                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17005             } else if (*p == '+' || isalpha(*p)) {
17006                 if (j >= gameInfo.boardWidth) return FALSE;
17007                 if(*p=='+') {
17008                     piece = CharToPiece(*++p);
17009                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17010                     piece = (ChessSquare) (PROMOTED piece ); p++;
17011                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17012                 } else piece = CharToPiece(*p++);
17013
17014                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17015                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17016                     piece = (ChessSquare) (PROMOTED piece);
17017                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17018                     p++;
17019                 }
17020                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17021             } else {
17022                 return FALSE;
17023             }
17024         }
17025     }
17026     while (*p == '/' || *p == ' ') p++;
17027
17028     /* [HGM] look for Crazyhouse holdings here */
17029     while(*p==' ') p++;
17030     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17031         if(*p == '[') p++;
17032         if(*p == '-' ) p++; /* empty holdings */ else {
17033             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17034             /* if we would allow FEN reading to set board size, we would   */
17035             /* have to add holdings and shift the board read so far here   */
17036             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17037                 p++;
17038                 if((int) piece >= (int) BlackPawn ) {
17039                     i = (int)piece - (int)BlackPawn;
17040                     i = PieceToNumber((ChessSquare)i);
17041                     if( i >= gameInfo.holdingsSize ) return FALSE;
17042                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17043                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17044                 } else {
17045                     i = (int)piece - (int)WhitePawn;
17046                     i = PieceToNumber((ChessSquare)i);
17047                     if( i >= gameInfo.holdingsSize ) return FALSE;
17048                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17049                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17050                 }
17051             }
17052         }
17053         if(*p == ']') p++;
17054     }
17055
17056     while(*p == ' ') p++;
17057
17058     /* Active color */
17059     c = *p++;
17060     if(appData.colorNickNames) {
17061       if( c == appData.colorNickNames[0] ) c = 'w'; else
17062       if( c == appData.colorNickNames[1] ) c = 'b';
17063     }
17064     switch (c) {
17065       case 'w':
17066         *blackPlaysFirst = FALSE;
17067         break;
17068       case 'b':
17069         *blackPlaysFirst = TRUE;
17070         break;
17071       default:
17072         return FALSE;
17073     }
17074
17075     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17076     /* return the extra info in global variiables             */
17077
17078     /* set defaults in case FEN is incomplete */
17079     board[EP_STATUS] = EP_UNKNOWN;
17080     for(i=0; i<nrCastlingRights; i++ ) {
17081         board[CASTLING][i] =
17082             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17083     }   /* assume possible unless obviously impossible */
17084     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17085     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17086     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17087                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17088     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17089     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17090     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17091                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17092     FENrulePlies = 0;
17093
17094     while(*p==' ') p++;
17095     if(nrCastlingRights) {
17096       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17097       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17098           /* castling indicator present, so default becomes no castlings */
17099           for(i=0; i<nrCastlingRights; i++ ) {
17100                  board[CASTLING][i] = NoRights;
17101           }
17102       }
17103       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17104              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17105              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17106              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17107         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17108
17109         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17110             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17111             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17112         }
17113         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17114             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17115         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17116                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17117         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17118                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17119         switch(c) {
17120           case'K':
17121               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17122               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17123               board[CASTLING][2] = whiteKingFile;
17124               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17125               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17126               break;
17127           case'Q':
17128               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17129               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17130               board[CASTLING][2] = whiteKingFile;
17131               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17132               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17133               break;
17134           case'k':
17135               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17136               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17137               board[CASTLING][5] = blackKingFile;
17138               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17139               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17140               break;
17141           case'q':
17142               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17143               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17144               board[CASTLING][5] = blackKingFile;
17145               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17146               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17147           case '-':
17148               break;
17149           default: /* FRC castlings */
17150               if(c >= 'a') { /* black rights */
17151                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17152                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17153                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17154                   if(i == BOARD_RGHT) break;
17155                   board[CASTLING][5] = i;
17156                   c -= AAA;
17157                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17158                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17159                   if(c > i)
17160                       board[CASTLING][3] = c;
17161                   else
17162                       board[CASTLING][4] = c;
17163               } else { /* white rights */
17164                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17165                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17166                     if(board[0][i] == WhiteKing) break;
17167                   if(i == BOARD_RGHT) break;
17168                   board[CASTLING][2] = i;
17169                   c -= AAA - 'a' + 'A';
17170                   if(board[0][c] >= WhiteKing) break;
17171                   if(c > i)
17172                       board[CASTLING][0] = c;
17173                   else
17174                       board[CASTLING][1] = c;
17175               }
17176         }
17177       }
17178       for(i=0; i<nrCastlingRights; i++)
17179         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17180       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17181     if (appData.debugMode) {
17182         fprintf(debugFP, "FEN castling rights:");
17183         for(i=0; i<nrCastlingRights; i++)
17184         fprintf(debugFP, " %d", board[CASTLING][i]);
17185         fprintf(debugFP, "\n");
17186     }
17187
17188       while(*p==' ') p++;
17189     }
17190
17191     /* read e.p. field in games that know e.p. capture */
17192     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17193        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17194       if(*p=='-') {
17195         p++; board[EP_STATUS] = EP_NONE;
17196       } else {
17197          char c = *p++ - AAA;
17198
17199          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17200          if(*p >= '0' && *p <='9') p++;
17201          board[EP_STATUS] = c;
17202       }
17203     }
17204
17205
17206     if(sscanf(p, "%d", &i) == 1) {
17207         FENrulePlies = i; /* 50-move ply counter */
17208         /* (The move number is still ignored)    */
17209     }
17210
17211     return TRUE;
17212 }
17213
17214 void
17215 EditPositionPasteFEN (char *fen)
17216 {
17217   if (fen != NULL) {
17218     Board initial_position;
17219
17220     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17221       DisplayError(_("Bad FEN position in clipboard"), 0);
17222       return ;
17223     } else {
17224       int savedBlackPlaysFirst = blackPlaysFirst;
17225       EditPositionEvent();
17226       blackPlaysFirst = savedBlackPlaysFirst;
17227       CopyBoard(boards[0], initial_position);
17228       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17229       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17230       DisplayBothClocks();
17231       DrawPosition(FALSE, boards[currentMove]);
17232     }
17233   }
17234 }
17235
17236 static char cseq[12] = "\\   ";
17237
17238 Boolean
17239 set_cont_sequence (char *new_seq)
17240 {
17241     int len;
17242     Boolean ret;
17243
17244     // handle bad attempts to set the sequence
17245         if (!new_seq)
17246                 return 0; // acceptable error - no debug
17247
17248     len = strlen(new_seq);
17249     ret = (len > 0) && (len < sizeof(cseq));
17250     if (ret)
17251       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17252     else if (appData.debugMode)
17253       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17254     return ret;
17255 }
17256
17257 /*
17258     reformat a source message so words don't cross the width boundary.  internal
17259     newlines are not removed.  returns the wrapped size (no null character unless
17260     included in source message).  If dest is NULL, only calculate the size required
17261     for the dest buffer.  lp argument indicats line position upon entry, and it's
17262     passed back upon exit.
17263 */
17264 int
17265 wrap (char *dest, char *src, int count, int width, int *lp)
17266 {
17267     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17268
17269     cseq_len = strlen(cseq);
17270     old_line = line = *lp;
17271     ansi = len = clen = 0;
17272
17273     for (i=0; i < count; i++)
17274     {
17275         if (src[i] == '\033')
17276             ansi = 1;
17277
17278         // if we hit the width, back up
17279         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17280         {
17281             // store i & len in case the word is too long
17282             old_i = i, old_len = len;
17283
17284             // find the end of the last word
17285             while (i && src[i] != ' ' && src[i] != '\n')
17286             {
17287                 i--;
17288                 len--;
17289             }
17290
17291             // word too long?  restore i & len before splitting it
17292             if ((old_i-i+clen) >= width)
17293             {
17294                 i = old_i;
17295                 len = old_len;
17296             }
17297
17298             // extra space?
17299             if (i && src[i-1] == ' ')
17300                 len--;
17301
17302             if (src[i] != ' ' && src[i] != '\n')
17303             {
17304                 i--;
17305                 if (len)
17306                     len--;
17307             }
17308
17309             // now append the newline and continuation sequence
17310             if (dest)
17311                 dest[len] = '\n';
17312             len++;
17313             if (dest)
17314                 strncpy(dest+len, cseq, cseq_len);
17315             len += cseq_len;
17316             line = cseq_len;
17317             clen = cseq_len;
17318             continue;
17319         }
17320
17321         if (dest)
17322             dest[len] = src[i];
17323         len++;
17324         if (!ansi)
17325             line++;
17326         if (src[i] == '\n')
17327             line = 0;
17328         if (src[i] == 'm')
17329             ansi = 0;
17330     }
17331     if (dest && appData.debugMode)
17332     {
17333         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17334             count, width, line, len, *lp);
17335         show_bytes(debugFP, src, count);
17336         fprintf(debugFP, "\ndest: ");
17337         show_bytes(debugFP, dest, len);
17338         fprintf(debugFP, "\n");
17339     }
17340     *lp = dest ? line : old_line;
17341
17342     return len;
17343 }
17344
17345 // [HGM] vari: routines for shelving variations
17346 Boolean modeRestore = FALSE;
17347
17348 void
17349 PushInner (int firstMove, int lastMove)
17350 {
17351         int i, j, nrMoves = lastMove - firstMove;
17352
17353         // push current tail of game on stack
17354         savedResult[storedGames] = gameInfo.result;
17355         savedDetails[storedGames] = gameInfo.resultDetails;
17356         gameInfo.resultDetails = NULL;
17357         savedFirst[storedGames] = firstMove;
17358         savedLast [storedGames] = lastMove;
17359         savedFramePtr[storedGames] = framePtr;
17360         framePtr -= nrMoves; // reserve space for the boards
17361         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17362             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17363             for(j=0; j<MOVE_LEN; j++)
17364                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17365             for(j=0; j<2*MOVE_LEN; j++)
17366                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17367             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17368             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17369             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17370             pvInfoList[firstMove+i-1].depth = 0;
17371             commentList[framePtr+i] = commentList[firstMove+i];
17372             commentList[firstMove+i] = NULL;
17373         }
17374
17375         storedGames++;
17376         forwardMostMove = firstMove; // truncate game so we can start variation
17377 }
17378
17379 void
17380 PushTail (int firstMove, int lastMove)
17381 {
17382         if(appData.icsActive) { // only in local mode
17383                 forwardMostMove = currentMove; // mimic old ICS behavior
17384                 return;
17385         }
17386         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17387
17388         PushInner(firstMove, lastMove);
17389         if(storedGames == 1) GreyRevert(FALSE);
17390         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17391 }
17392
17393 void
17394 PopInner (Boolean annotate)
17395 {
17396         int i, j, nrMoves;
17397         char buf[8000], moveBuf[20];
17398
17399         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17400         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17401         nrMoves = savedLast[storedGames] - currentMove;
17402         if(annotate) {
17403                 int cnt = 10;
17404                 if(!WhiteOnMove(currentMove))
17405                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17406                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17407                 for(i=currentMove; i<forwardMostMove; i++) {
17408                         if(WhiteOnMove(i))
17409                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17410                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17411                         strcat(buf, moveBuf);
17412                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17413                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17414                 }
17415                 strcat(buf, ")");
17416         }
17417         for(i=1; i<=nrMoves; i++) { // copy last variation back
17418             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17419             for(j=0; j<MOVE_LEN; j++)
17420                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17421             for(j=0; j<2*MOVE_LEN; j++)
17422                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17423             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17424             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17425             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17426             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17427             commentList[currentMove+i] = commentList[framePtr+i];
17428             commentList[framePtr+i] = NULL;
17429         }
17430         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17431         framePtr = savedFramePtr[storedGames];
17432         gameInfo.result = savedResult[storedGames];
17433         if(gameInfo.resultDetails != NULL) {
17434             free(gameInfo.resultDetails);
17435       }
17436         gameInfo.resultDetails = savedDetails[storedGames];
17437         forwardMostMove = currentMove + nrMoves;
17438 }
17439
17440 Boolean
17441 PopTail (Boolean annotate)
17442 {
17443         if(appData.icsActive) return FALSE; // only in local mode
17444         if(!storedGames) return FALSE; // sanity
17445         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17446
17447         PopInner(annotate);
17448         if(currentMove < forwardMostMove) ForwardEvent(); else
17449         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17450
17451         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17452         return TRUE;
17453 }
17454
17455 void
17456 CleanupTail ()
17457 {       // remove all shelved variations
17458         int i;
17459         for(i=0; i<storedGames; i++) {
17460             if(savedDetails[i])
17461                 free(savedDetails[i]);
17462             savedDetails[i] = NULL;
17463         }
17464         for(i=framePtr; i<MAX_MOVES; i++) {
17465                 if(commentList[i]) free(commentList[i]);
17466                 commentList[i] = NULL;
17467         }
17468         framePtr = MAX_MOVES-1;
17469         storedGames = 0;
17470 }
17471
17472 void
17473 LoadVariation (int index, char *text)
17474 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17475         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17476         int level = 0, move;
17477
17478         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17479         // first find outermost bracketing variation
17480         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17481             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17482                 if(*p == '{') wait = '}'; else
17483                 if(*p == '[') wait = ']'; else
17484                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17485                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17486             }
17487             if(*p == wait) wait = NULLCHAR; // closing ]} found
17488             p++;
17489         }
17490         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17491         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17492         end[1] = NULLCHAR; // clip off comment beyond variation
17493         ToNrEvent(currentMove-1);
17494         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17495         // kludge: use ParsePV() to append variation to game
17496         move = currentMove;
17497         ParsePV(start, TRUE, TRUE);
17498         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17499         ClearPremoveHighlights();
17500         CommentPopDown();
17501         ToNrEvent(currentMove+1);
17502 }
17503
17504 void
17505 LoadTheme ()
17506 {
17507     char *p, *q, buf[MSG_SIZ];
17508     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17509         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17510         ParseArgsFromString(buf);
17511         ActivateTheme(TRUE); // also redo colors
17512         return;
17513     }
17514     p = nickName;
17515     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17516     {
17517         int len;
17518         q = appData.themeNames;
17519         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17520       if(appData.useBitmaps) {
17521         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17522                 appData.liteBackTextureFile, appData.darkBackTextureFile, 
17523                 appData.liteBackTextureMode,
17524                 appData.darkBackTextureMode );
17525       } else {
17526         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17527                 Col2Text(2),   // lightSquareColor
17528                 Col2Text(3) ); // darkSquareColor
17529       }
17530       if(appData.useBorder) {
17531         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17532                 appData.border);
17533       } else {
17534         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17535       }
17536       if(appData.useFont) {
17537         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17538                 appData.renderPiecesWithFont,
17539                 appData.fontToPieceTable,
17540                 Col2Text(9),    // appData.fontBackColorWhite
17541                 Col2Text(10) ); // appData.fontForeColorBlack
17542       } else {
17543         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17544                 appData.pieceDirectory);
17545         if(!appData.pieceDirectory[0])
17546           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17547                 Col2Text(0),   // whitePieceColor
17548                 Col2Text(1) ); // blackPieceColor
17549       }
17550       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17551                 Col2Text(4),   // highlightSquareColor
17552                 Col2Text(5) ); // premoveHighlightColor
17553         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17554         if(insert != q) insert[-1] = NULLCHAR;
17555         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17556         if(q)   free(q);
17557     }
17558     ActivateTheme(FALSE);
17559 }