4c302506665e1ea5660285e9a1b4e8b840a6590d
[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, 2013 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 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char lastMsg[MSG_SIZ];
273 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
276 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
277
278 /* States for ics_getting_history */
279 #define H_FALSE 0
280 #define H_REQUESTED 1
281 #define H_GOT_REQ_HEADER 2
282 #define H_GOT_UNREQ_HEADER 3
283 #define H_GETTING_MOVES 4
284 #define H_GOT_UNWANTED_HEADER 5
285
286 /* whosays values for GameEnds */
287 #define GE_ICS 0
288 #define GE_ENGINE 1
289 #define GE_PLAYER 2
290 #define GE_FILE 3
291 #define GE_XBOARD 4
292 #define GE_ENGINE1 5
293 #define GE_ENGINE2 6
294
295 /* Maximum number of games in a cmail message */
296 #define CMAIL_MAX_GAMES 20
297
298 /* Different types of move when calling RegisterMove */
299 #define CMAIL_MOVE   0
300 #define CMAIL_RESIGN 1
301 #define CMAIL_DRAW   2
302 #define CMAIL_ACCEPT 3
303
304 /* Different types of result to remember for each game */
305 #define CMAIL_NOT_RESULT 0
306 #define CMAIL_OLD_RESULT 1
307 #define CMAIL_NEW_RESULT 2
308
309 /* Telnet protocol constants */
310 #define TN_WILL 0373
311 #define TN_WONT 0374
312 #define TN_DO   0375
313 #define TN_DONT 0376
314 #define TN_IAC  0377
315 #define TN_ECHO 0001
316 #define TN_SGA  0003
317 #define TN_PORT 23
318
319 char*
320 safeStrCpy (char *dst, const char *src, size_t count)
321 { // [HGM] made safe
322   int i;
323   assert( dst != NULL );
324   assert( src != NULL );
325   assert( count > 0 );
326
327   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
328   if(  i == count && dst[count-1] != NULLCHAR)
329     {
330       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
331       if(appData.debugMode)
332         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333     }
334
335   return dst;
336 }
337
338 /* Some compiler can't cast u64 to double
339  * This function do the job for us:
340
341  * We use the highest bit for cast, this only
342  * works if the highest bit is not
343  * in use (This should not happen)
344  *
345  * We used this for all compiler
346  */
347 double
348 u64ToDouble (u64 value)
349 {
350   double r;
351   u64 tmp = value & u64Const(0x7fffffffffffffff);
352   r = (double)(s64)tmp;
353   if (value & u64Const(0x8000000000000000))
354        r +=  9.2233720368547758080e18; /* 2^63 */
355  return r;
356 }
357
358 /* Fake up flags for now, as we aren't keeping track of castling
359    availability yet. [HGM] Change of logic: the flag now only
360    indicates the type of castlings allowed by the rule of the game.
361    The actual rights themselves are maintained in the array
362    castlingRights, as part of the game history, and are not probed
363    by this function.
364  */
365 int
366 PosFlags (index)
367 {
368   int flags = F_ALL_CASTLE_OK;
369   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
370   switch (gameInfo.variant) {
371   case VariantSuicide:
372     flags &= ~F_ALL_CASTLE_OK;
373   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
374     flags |= F_IGNORE_CHECK;
375   case VariantLosers:
376     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377     break;
378   case VariantAtomic:
379     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380     break;
381   case VariantKriegspiel:
382     flags |= F_KRIEGSPIEL_CAPTURE;
383     break;
384   case VariantCapaRandom:
385   case VariantFischeRandom:
386     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
387   case VariantNoCastle:
388   case VariantShatranj:
389   case VariantCourier:
390   case VariantMakruk:
391   case VariantASEAN:
392   case VariantGrand:
393     flags &= ~F_ALL_CASTLE_OK;
394     break;
395   default:
396     break;
397   }
398   return flags;
399 }
400
401 FILE *gameFileFP, *debugFP, *serverFP;
402 char *currentDebugFile; // [HGM] debug split: to remember name
403
404 /*
405     [AS] Note: sometimes, the sscanf() function is used to parse the input
406     into a fixed-size buffer. Because of this, we must be prepared to
407     receive strings as long as the size of the input buffer, which is currently
408     set to 4K for Windows and 8K for the rest.
409     So, we must either allocate sufficiently large buffers here, or
410     reduce the size of the input buffer in the input reading part.
411 */
412
413 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
414 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
415 char thinkOutput1[MSG_SIZ*10];
416
417 ChessProgramState first, second, pairing;
418
419 /* premove variables */
420 int premoveToX = 0;
421 int premoveToY = 0;
422 int premoveFromX = 0;
423 int premoveFromY = 0;
424 int premovePromoChar = 0;
425 int gotPremove = 0;
426 Boolean alarmSounded;
427 /* end premove variables */
428
429 char *ics_prefix = "$";
430 enum ICS_TYPE ics_type = ICS_GENERIC;
431
432 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
433 int pauseExamForwardMostMove = 0;
434 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
435 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
436 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
437 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
438 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
439 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
440 int whiteFlag = FALSE, blackFlag = FALSE;
441 int userOfferedDraw = FALSE;
442 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
443 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
444 int cmailMoveType[CMAIL_MAX_GAMES];
445 long ics_clock_paused = 0;
446 ProcRef icsPR = NoProc, cmailPR = NoProc;
447 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
448 GameMode gameMode = BeginningOfGame;
449 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
450 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
451 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
452 int hiddenThinkOutputState = 0; /* [AS] */
453 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
454 int adjudicateLossPlies = 6;
455 char white_holding[64], black_holding[64];
456 TimeMark lastNodeCountTime;
457 long lastNodeCount=0;
458 int shiftKey, controlKey; // [HGM] set by mouse handler
459
460 int have_sent_ICS_logon = 0;
461 int movesPerSession;
462 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
463 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
464 Boolean adjustedClock;
465 long timeControl_2; /* [AS] Allow separate time controls */
466 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
467 long timeRemaining[2][MAX_MOVES];
468 int matchGame = 0, nextGame = 0, roundNr = 0;
469 Boolean waitingForGame = FALSE, startingEngine = FALSE;
470 TimeMark programStartTime, pauseStart;
471 char ics_handle[MSG_SIZ];
472 int have_set_title = 0;
473
474 /* animateTraining preserves the state of appData.animate
475  * when Training mode is activated. This allows the
476  * response to be animated when appData.animate == TRUE and
477  * appData.animateDragging == TRUE.
478  */
479 Boolean animateTraining;
480
481 GameInfo gameInfo;
482
483 AppData appData;
484
485 Board boards[MAX_MOVES];
486 /* [HGM] Following 7 needed for accurate legality tests: */
487 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
488 signed char  initialRights[BOARD_FILES];
489 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
490 int   initialRulePlies, FENrulePlies;
491 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
492 int loadFlag = 0;
493 Boolean shuffleOpenings;
494 int mute; // mute all sounds
495
496 // [HGM] vari: next 12 to save and restore variations
497 #define MAX_VARIATIONS 10
498 int framePtr = MAX_MOVES-1; // points to free stack entry
499 int storedGames = 0;
500 int savedFirst[MAX_VARIATIONS];
501 int savedLast[MAX_VARIATIONS];
502 int savedFramePtr[MAX_VARIATIONS];
503 char *savedDetails[MAX_VARIATIONS];
504 ChessMove savedResult[MAX_VARIATIONS];
505
506 void PushTail P((int firstMove, int lastMove));
507 Boolean PopTail P((Boolean annotate));
508 void PushInner P((int firstMove, int lastMove));
509 void PopInner P((Boolean annotate));
510 void CleanupTail P((void));
511
512 ChessSquare  FIDEArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackBishop, BlackKnight, BlackRook }
517 };
518
519 ChessSquare twoKingsArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
522     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
523         BlackKing, BlackKing, BlackKnight, BlackRook }
524 };
525
526 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
528         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
529     { BlackRook, BlackMan, BlackBishop, BlackQueen,
530         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
531 };
532
533 ChessSquare SpartanArray[2][BOARD_FILES] = {
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
537         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
538 };
539
540 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
544         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
545 };
546
547 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
549         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
551         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
552 };
553
554 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
555     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
556         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackMan, BlackFerz,
558         BlackKing, BlackMan, BlackKnight, BlackRook }
559 };
560
561 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
562     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
563         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
564     { BlackRook, BlackKnight, BlackMan, BlackFerz,
565         BlackKing, BlackMan, BlackKnight, BlackRook }
566 };
567
568
569 #if (BOARD_FILES>=10)
570 ChessSquare ShogiArray[2][BOARD_FILES] = {
571     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
572         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
573     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
574         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
575 };
576
577 ChessSquare XiangqiArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
579         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
581         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare CapablancaArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
586         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
588         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
589 };
590
591 ChessSquare GreatArray[2][BOARD_FILES] = {
592     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
593         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
594     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
595         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
596 };
597
598 ChessSquare JanusArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
600         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
601     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
602         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
603 };
604
605 ChessSquare GrandArray[2][BOARD_FILES] = {
606     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
607         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
608     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
609         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
610 };
611
612 #ifdef GOTHIC
613 ChessSquare GothicArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
615         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
617         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !GOTHIC
620 #define GothicArray CapablancaArray
621 #endif // !GOTHIC
622
623 #ifdef FALCON
624 ChessSquare FalconArray[2][BOARD_FILES] = {
625     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
626         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
627     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
628         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
629 };
630 #else // !FALCON
631 #define FalconArray CapablancaArray
632 #endif // !FALCON
633
634 #else // !(BOARD_FILES>=10)
635 #define XiangqiPosition FIDEArray
636 #define CapablancaArray FIDEArray
637 #define GothicArray FIDEArray
638 #define GreatArray FIDEArray
639 #endif // !(BOARD_FILES>=10)
640
641 #if (BOARD_FILES>=12)
642 ChessSquare CourierArray[2][BOARD_FILES] = {
643     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
644         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
645     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
646         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
647 };
648 #else // !(BOARD_FILES>=12)
649 #define CourierArray CapablancaArray
650 #endif // !(BOARD_FILES>=12)
651
652
653 Board initialPosition;
654
655
656 /* Convert str to a rating. Checks for special cases of "----",
657
658    "++++", etc. Also strips ()'s */
659 int
660 string_to_rating (char *str)
661 {
662   while(*str && !isdigit(*str)) ++str;
663   if (!*str)
664     return 0;   /* One of the special "no rating" cases */
665   else
666     return atoi(str);
667 }
668
669 void
670 ClearProgramStats ()
671 {
672     /* Init programStats */
673     programStats.movelist[0] = 0;
674     programStats.depth = 0;
675     programStats.nr_moves = 0;
676     programStats.moves_left = 0;
677     programStats.nodes = 0;
678     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
679     programStats.score = 0;
680     programStats.got_only_move = 0;
681     programStats.got_fail = 0;
682     programStats.line_is_book = 0;
683 }
684
685 void
686 CommonEngineInit ()
687 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695
696     first.other = &second;
697     second.other = &first;
698
699     { float norm = 1;
700         if(appData.timeOddsMode) {
701             norm = appData.timeOdds[0];
702             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703         }
704         first.timeOdds  = appData.timeOdds[0]/norm;
705         second.timeOdds = appData.timeOdds[1]/norm;
706     }
707
708     if(programVersion) free(programVersion);
709     if (appData.noChessProgram) {
710         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711         sprintf(programVersion, "%s", PACKAGE_STRING);
712     } else {
713       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
716     }
717 }
718
719 void
720 UnloadEngine (ChessProgramState *cps)
721 {
722         /* Kill off first chess program */
723         if (cps->isr != NULL)
724           RemoveInputSource(cps->isr);
725         cps->isr = NULL;
726
727         if (cps->pr != NoProc) {
728             ExitAnalyzeMode();
729             DoSleep( appData.delayBeforeQuit );
730             SendToProgram("quit\n", cps);
731             DoSleep( appData.delayAfterQuit );
732             DestroyChildProcess(cps->pr, cps->useSigterm);
733         }
734         cps->pr = NoProc;
735         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
736 }
737
738 void
739 ClearOptions (ChessProgramState *cps)
740 {
741     int i;
742     cps->nrOptions = cps->comboCnt = 0;
743     for(i=0; i<MAX_OPTIONS; i++) {
744         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745         cps->option[i].textValue = 0;
746     }
747 }
748
749 char *engineNames[] = {
750   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
751      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
752 N_("first"),
753   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
754      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
755 N_("second")
756 };
757
758 void
759 InitEngine (ChessProgramState *cps, int n)
760 {   // [HGM] all engine initialiation put in a function that does one engine
761
762     ClearOptions(cps);
763
764     cps->which = engineNames[n];
765     cps->maybeThinking = FALSE;
766     cps->pr = NoProc;
767     cps->isr = NULL;
768     cps->sendTime = 2;
769     cps->sendDrawOffers = 1;
770
771     cps->program = appData.chessProgram[n];
772     cps->host = appData.host[n];
773     cps->dir = appData.directory[n];
774     cps->initString = appData.engInitString[n];
775     cps->computerString = appData.computerString[n];
776     cps->useSigint  = TRUE;
777     cps->useSigterm = TRUE;
778     cps->reuse = appData.reuse[n];
779     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
780     cps->useSetboard = FALSE;
781     cps->useSAN = FALSE;
782     cps->usePing = FALSE;
783     cps->lastPing = 0;
784     cps->lastPong = 0;
785     cps->usePlayother = FALSE;
786     cps->useColors = TRUE;
787     cps->useUsermove = FALSE;
788     cps->sendICS = FALSE;
789     cps->sendName = appData.icsActive;
790     cps->sdKludge = FALSE;
791     cps->stKludge = FALSE;
792     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
793     TidyProgramName(cps->program, cps->host, cps->tidy);
794     cps->matchWins = 0;
795     ASSIGN(cps->variants, appData.variant);
796     cps->analysisSupport = 2; /* detect */
797     cps->analyzing = FALSE;
798     cps->initDone = FALSE;
799     cps->reload = FALSE;
800
801     /* New features added by Tord: */
802     cps->useFEN960 = FALSE;
803     cps->useOOCastle = TRUE;
804     /* End of new features added by Tord. */
805     cps->fenOverride  = appData.fenOverride[n];
806
807     /* [HGM] time odds: set factor for each machine */
808     cps->timeOdds  = appData.timeOdds[n];
809
810     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
811     cps->accumulateTC = appData.accumulateTC[n];
812     cps->maxNrOfSessions = 1;
813
814     /* [HGM] debug */
815     cps->debug = FALSE;
816
817     cps->supportsNPS = UNKNOWN;
818     cps->memSize = FALSE;
819     cps->maxCores = FALSE;
820     ASSIGN(cps->egtFormats, "");
821
822     /* [HGM] options */
823     cps->optionSettings  = appData.engOptions[n];
824
825     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
826     cps->isUCI = appData.isUCI[n]; /* [AS] */
827     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
828
829     if (appData.protocolVersion[n] > PROTOVER
830         || appData.protocolVersion[n] < 1)
831       {
832         char buf[MSG_SIZ];
833         int len;
834
835         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
836                        appData.protocolVersion[n]);
837         if( (len >= MSG_SIZ) && appData.debugMode )
838           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
839
840         DisplayFatalError(buf, 0, 2);
841       }
842     else
843       {
844         cps->protocolVersion = appData.protocolVersion[n];
845       }
846
847     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
848     ParseFeatures(appData.featureDefaults, cps);
849 }
850
851 ChessProgramState *savCps;
852
853 GameMode oldMode;
854
855 void
856 LoadEngine ()
857 {
858     int i;
859     if(WaitForEngine(savCps, LoadEngine)) return;
860     CommonEngineInit(); // recalculate time odds
861     if(gameInfo.variant != StringToVariant(appData.variant)) {
862         // we changed variant when loading the engine; this forces us to reset
863         Reset(TRUE, savCps != &first);
864         oldMode = BeginningOfGame; // to prevent restoring old mode
865     }
866     InitChessProgram(savCps, FALSE);
867     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
868     DisplayMessage("", "");
869     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
870     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
871     ThawUI();
872     SetGNUMode();
873     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
874 }
875
876 void
877 ReplaceEngine (ChessProgramState *cps, int n)
878 {
879     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
880     keepInfo = 1;
881     if(oldMode != BeginningOfGame) EditGameEvent();
882     keepInfo = 0;
883     UnloadEngine(cps);
884     appData.noChessProgram = FALSE;
885     appData.clockMode = TRUE;
886     InitEngine(cps, n);
887     UpdateLogos(TRUE);
888     if(n) return; // only startup first engine immediately; second can wait
889     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
890     LoadEngine();
891 }
892
893 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
894 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
895
896 static char resetOptions[] =
897         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
898         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
899         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
900         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
901
902 void
903 FloatToFront(char **list, char *engineLine)
904 {
905     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
906     int i=0;
907     if(appData.recentEngines <= 0) return;
908     TidyProgramName(engineLine, "localhost", tidy+1);
909     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
910     strncpy(buf+1, *list, MSG_SIZ-50);
911     if(p = strstr(buf, tidy)) { // tidy name appears in list
912         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
913         while(*p++ = *++q); // squeeze out
914     }
915     strcat(tidy, buf+1); // put list behind tidy name
916     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
917     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
918     ASSIGN(*list, tidy+1);
919 }
920
921 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
922
923 void
924 Load (ChessProgramState *cps, int i)
925 {
926     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
927     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
928         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
929         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
930         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
931         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
932         appData.firstProtocolVersion = PROTOVER;
933         ParseArgsFromString(buf);
934         SwapEngines(i);
935         ReplaceEngine(cps, i);
936         FloatToFront(&appData.recentEngineList, engineLine);
937         return;
938     }
939     p = engineName;
940     while(q = strchr(p, SLASH)) p = q+1;
941     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
942     if(engineDir[0] != NULLCHAR) {
943         ASSIGN(appData.directory[i], engineDir); p = engineName;
944     } else if(p != engineName) { // derive directory from engine path, when not given
945         p[-1] = 0;
946         ASSIGN(appData.directory[i], engineName);
947         p[-1] = SLASH;
948         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
949     } else { ASSIGN(appData.directory[i], "."); }
950     if(params[0]) {
951         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
952         snprintf(command, MSG_SIZ, "%s %s", p, params);
953         p = command;
954     }
955     ASSIGN(appData.chessProgram[i], p);
956     appData.isUCI[i] = isUCI;
957     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
958     appData.hasOwnBookUCI[i] = hasBook;
959     if(!nickName[0]) useNick = FALSE;
960     if(useNick) ASSIGN(appData.pgnName[i], nickName);
961     if(addToList) {
962         int len;
963         char quote;
964         q = firstChessProgramNames;
965         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
966         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
967         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
968                         quote, p, quote, appData.directory[i],
969                         useNick ? " -fn \"" : "",
970                         useNick ? nickName : "",
971                         useNick ? "\"" : "",
972                         v1 ? " -firstProtocolVersion 1" : "",
973                         hasBook ? "" : " -fNoOwnBookUCI",
974                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
975                         storeVariant ? " -variant " : "",
976                         storeVariant ? VariantName(gameInfo.variant) : "");
977         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
978         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
979         if(insert != q) insert[-1] = NULLCHAR;
980         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
981         if(q)   free(q);
982         FloatToFront(&appData.recentEngineList, buf);
983     }
984     ReplaceEngine(cps, i);
985 }
986
987 void
988 InitTimeControls ()
989 {
990     int matched, min, sec;
991     /*
992      * Parse timeControl resource
993      */
994     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
995                           appData.movesPerSession)) {
996         char buf[MSG_SIZ];
997         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
998         DisplayFatalError(buf, 0, 2);
999     }
1000
1001     /*
1002      * Parse searchTime resource
1003      */
1004     if (*appData.searchTime != NULLCHAR) {
1005         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1006         if (matched == 1) {
1007             searchTime = min * 60;
1008         } else if (matched == 2) {
1009             searchTime = min * 60 + sec;
1010         } else {
1011             char buf[MSG_SIZ];
1012             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1013             DisplayFatalError(buf, 0, 2);
1014         }
1015     }
1016 }
1017
1018 void
1019 InitBackEnd1 ()
1020 {
1021
1022     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1023     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1024
1025     GetTimeMark(&programStartTime);
1026     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1027     appData.seedBase = random() + (random()<<15);
1028     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1029
1030     ClearProgramStats();
1031     programStats.ok_to_send = 1;
1032     programStats.seen_stat = 0;
1033
1034     /*
1035      * Initialize game list
1036      */
1037     ListNew(&gameList);
1038
1039
1040     /*
1041      * Internet chess server status
1042      */
1043     if (appData.icsActive) {
1044         appData.matchMode = FALSE;
1045         appData.matchGames = 0;
1046 #if ZIPPY
1047         appData.noChessProgram = !appData.zippyPlay;
1048 #else
1049         appData.zippyPlay = FALSE;
1050         appData.zippyTalk = FALSE;
1051         appData.noChessProgram = TRUE;
1052 #endif
1053         if (*appData.icsHelper != NULLCHAR) {
1054             appData.useTelnet = TRUE;
1055             appData.telnetProgram = appData.icsHelper;
1056         }
1057     } else {
1058         appData.zippyTalk = appData.zippyPlay = FALSE;
1059     }
1060
1061     /* [AS] Initialize pv info list [HGM] and game state */
1062     {
1063         int i, j;
1064
1065         for( i=0; i<=framePtr; i++ ) {
1066             pvInfoList[i].depth = -1;
1067             boards[i][EP_STATUS] = EP_NONE;
1068             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1069         }
1070     }
1071
1072     InitTimeControls();
1073
1074     /* [AS] Adjudication threshold */
1075     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1076
1077     InitEngine(&first, 0);
1078     InitEngine(&second, 1);
1079     CommonEngineInit();
1080
1081     pairing.which = "pairing"; // pairing engine
1082     pairing.pr = NoProc;
1083     pairing.isr = NULL;
1084     pairing.program = appData.pairingEngine;
1085     pairing.host = "localhost";
1086     pairing.dir = ".";
1087
1088     if (appData.icsActive) {
1089         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1090     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1091         appData.clockMode = FALSE;
1092         first.sendTime = second.sendTime = 0;
1093     }
1094
1095 #if ZIPPY
1096     /* Override some settings from environment variables, for backward
1097        compatibility.  Unfortunately it's not feasible to have the env
1098        vars just set defaults, at least in xboard.  Ugh.
1099     */
1100     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1101       ZippyInit();
1102     }
1103 #endif
1104
1105     if (!appData.icsActive) {
1106       char buf[MSG_SIZ];
1107       int len;
1108
1109       /* Check for variants that are supported only in ICS mode,
1110          or not at all.  Some that are accepted here nevertheless
1111          have bugs; see comments below.
1112       */
1113       VariantClass variant = StringToVariant(appData.variant);
1114       switch (variant) {
1115       case VariantBughouse:     /* need four players and two boards */
1116       case VariantKriegspiel:   /* need to hide pieces and move details */
1117         /* case VariantFischeRandom: (Fabien: moved below) */
1118         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), 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 VariantUnknown:
1126       case VariantLoadable:
1127       case Variant29:
1128       case Variant30:
1129       case Variant31:
1130       case Variant32:
1131       case Variant33:
1132       case Variant34:
1133       case Variant35:
1134       case Variant36:
1135       default:
1136         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1137         if( (len >= MSG_SIZ) && appData.debugMode )
1138           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1139
1140         DisplayFatalError(buf, 0, 2);
1141         return;
1142
1143       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1144       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1145       case VariantGothic:     /* [HGM] should work */
1146       case VariantCapablanca: /* [HGM] should work */
1147       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1148       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1149       case VariantKnightmate: /* [HGM] should work */
1150       case VariantCylinder:   /* [HGM] untested */
1151       case VariantFalcon:     /* [HGM] untested */
1152       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1153                                  offboard interposition not understood */
1154       case VariantNormal:     /* definitely works! */
1155       case VariantWildCastle: /* pieces not automatically shuffled */
1156       case VariantNoCastle:   /* pieces not automatically shuffled */
1157       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1158       case VariantLosers:     /* should work except for win condition,
1159                                  and doesn't know captures are mandatory */
1160       case VariantSuicide:    /* should work except for win condition,
1161                                  and doesn't know captures are mandatory */
1162       case VariantGiveaway:   /* should work except for win condition,
1163                                  and doesn't know captures are mandatory */
1164       case VariantTwoKings:   /* should work */
1165       case VariantAtomic:     /* should work except for win condition */
1166       case Variant3Check:     /* should work except for win condition */
1167       case VariantShatranj:   /* should work except for all win conditions */
1168       case VariantMakruk:     /* should work except for draw countdown */
1169       case VariantASEAN :     /* should work except for draw countdown */
1170       case VariantBerolina:   /* might work if TestLegality is off */
1171       case VariantCapaRandom: /* should work */
1172       case VariantJanus:      /* should work */
1173       case VariantSuper:      /* experimental */
1174       case VariantGreat:      /* experimental, requires legality testing to be off */
1175       case VariantSChess:     /* S-Chess, should work */
1176       case VariantGrand:      /* should work */
1177       case VariantSpartan:    /* should work */
1178         break;
1179       }
1180     }
1181
1182 }
1183
1184 int
1185 NextIntegerFromString (char ** str, long * value)
1186 {
1187     int result = -1;
1188     char * s = *str;
1189
1190     while( *s == ' ' || *s == '\t' ) {
1191         s++;
1192     }
1193
1194     *value = 0;
1195
1196     if( *s >= '0' && *s <= '9' ) {
1197         while( *s >= '0' && *s <= '9' ) {
1198             *value = *value * 10 + (*s - '0');
1199             s++;
1200         }
1201
1202         result = 0;
1203     }
1204
1205     *str = s;
1206
1207     return result;
1208 }
1209
1210 int
1211 NextTimeControlFromString (char ** str, long * value)
1212 {
1213     long temp;
1214     int result = NextIntegerFromString( str, &temp );
1215
1216     if( result == 0 ) {
1217         *value = temp * 60; /* Minutes */
1218         if( **str == ':' ) {
1219             (*str)++;
1220             result = NextIntegerFromString( str, &temp );
1221             *value += temp; /* Seconds */
1222         }
1223     }
1224
1225     return result;
1226 }
1227
1228 int
1229 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1230 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1231     int result = -1, type = 0; long temp, temp2;
1232
1233     if(**str != ':') return -1; // old params remain in force!
1234     (*str)++;
1235     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1236     if( NextIntegerFromString( str, &temp ) ) return -1;
1237     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1238
1239     if(**str != '/') {
1240         /* time only: incremental or sudden-death time control */
1241         if(**str == '+') { /* increment follows; read it */
1242             (*str)++;
1243             if(**str == '!') type = *(*str)++; // Bronstein TC
1244             if(result = NextIntegerFromString( str, &temp2)) return -1;
1245             *inc = temp2 * 1000;
1246             if(**str == '.') { // read fraction of increment
1247                 char *start = ++(*str);
1248                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1249                 temp2 *= 1000;
1250                 while(start++ < *str) temp2 /= 10;
1251                 *inc += temp2;
1252             }
1253         } else *inc = 0;
1254         *moves = 0; *tc = temp * 1000; *incType = type;
1255         return 0;
1256     }
1257
1258     (*str)++; /* classical time control */
1259     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1260
1261     if(result == 0) {
1262         *moves = temp;
1263         *tc    = temp2 * 1000;
1264         *inc   = 0;
1265         *incType = type;
1266     }
1267     return result;
1268 }
1269
1270 int
1271 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1272 {   /* [HGM] get time to add from the multi-session time-control string */
1273     int incType, moves=1; /* kludge to force reading of first session */
1274     long time, increment;
1275     char *s = tcString;
1276
1277     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1278     do {
1279         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1280         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1281         if(movenr == -1) return time;    /* last move before new session     */
1282         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1283         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1284         if(!moves) return increment;     /* current session is incremental   */
1285         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1286     } while(movenr >= -1);               /* try again for next session       */
1287
1288     return 0; // no new time quota on this move
1289 }
1290
1291 int
1292 ParseTimeControl (char *tc, float ti, int mps)
1293 {
1294   long tc1;
1295   long tc2;
1296   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1297   int min, sec=0;
1298
1299   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1300   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1301       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1302   if(ti > 0) {
1303
1304     if(mps)
1305       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1306     else
1307       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1308   } else {
1309     if(mps)
1310       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1311     else
1312       snprintf(buf, MSG_SIZ, ":%s", mytc);
1313   }
1314   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1315
1316   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1317     return FALSE;
1318   }
1319
1320   if( *tc == '/' ) {
1321     /* Parse second time control */
1322     tc++;
1323
1324     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1325       return FALSE;
1326     }
1327
1328     if( tc2 == 0 ) {
1329       return FALSE;
1330     }
1331
1332     timeControl_2 = tc2 * 1000;
1333   }
1334   else {
1335     timeControl_2 = 0;
1336   }
1337
1338   if( tc1 == 0 ) {
1339     return FALSE;
1340   }
1341
1342   timeControl = tc1 * 1000;
1343
1344   if (ti >= 0) {
1345     timeIncrement = ti * 1000;  /* convert to ms */
1346     movesPerSession = 0;
1347   } else {
1348     timeIncrement = 0;
1349     movesPerSession = mps;
1350   }
1351   return TRUE;
1352 }
1353
1354 void
1355 InitBackEnd2 ()
1356 {
1357     if (appData.debugMode) {
1358 #    ifdef __GIT_VERSION
1359       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1360 #    else
1361       fprintf(debugFP, "Version: %s\n", programVersion);
1362 #    endif
1363     }
1364     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1365
1366     set_cont_sequence(appData.wrapContSeq);
1367     if (appData.matchGames > 0) {
1368         appData.matchMode = TRUE;
1369     } else if (appData.matchMode) {
1370         appData.matchGames = 1;
1371     }
1372     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1373         appData.matchGames = appData.sameColorGames;
1374     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1375         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1376         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1377     }
1378     Reset(TRUE, FALSE);
1379     if (appData.noChessProgram || first.protocolVersion == 1) {
1380       InitBackEnd3();
1381     } else {
1382       /* kludge: allow timeout for initial "feature" commands */
1383       FreezeUI();
1384       DisplayMessage("", _("Starting chess program"));
1385       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1386     }
1387 }
1388
1389 int
1390 CalculateIndex (int index, int gameNr)
1391 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1392     int res;
1393     if(index > 0) return index; // fixed nmber
1394     if(index == 0) return 1;
1395     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1396     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1397     return res;
1398 }
1399
1400 int
1401 LoadGameOrPosition (int gameNr)
1402 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1403     if (*appData.loadGameFile != NULLCHAR) {
1404         if (!LoadGameFromFile(appData.loadGameFile,
1405                 CalculateIndex(appData.loadGameIndex, gameNr),
1406                               appData.loadGameFile, FALSE)) {
1407             DisplayFatalError(_("Bad game file"), 0, 1);
1408             return 0;
1409         }
1410     } else if (*appData.loadPositionFile != NULLCHAR) {
1411         if (!LoadPositionFromFile(appData.loadPositionFile,
1412                 CalculateIndex(appData.loadPositionIndex, gameNr),
1413                                   appData.loadPositionFile)) {
1414             DisplayFatalError(_("Bad position file"), 0, 1);
1415             return 0;
1416         }
1417     }
1418     return 1;
1419 }
1420
1421 void
1422 ReserveGame (int gameNr, char resChar)
1423 {
1424     FILE *tf = fopen(appData.tourneyFile, "r+");
1425     char *p, *q, c, buf[MSG_SIZ];
1426     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1427     safeStrCpy(buf, lastMsg, MSG_SIZ);
1428     DisplayMessage(_("Pick new game"), "");
1429     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1430     ParseArgsFromFile(tf);
1431     p = q = appData.results;
1432     if(appData.debugMode) {
1433       char *r = appData.participants;
1434       fprintf(debugFP, "results = '%s'\n", p);
1435       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1436       fprintf(debugFP, "\n");
1437     }
1438     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1439     nextGame = q - p;
1440     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1441     safeStrCpy(q, p, strlen(p) + 2);
1442     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1443     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1444     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1445         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1446         q[nextGame] = '*';
1447     }
1448     fseek(tf, -(strlen(p)+4), SEEK_END);
1449     c = fgetc(tf);
1450     if(c != '"') // depending on DOS or Unix line endings we can be one off
1451          fseek(tf, -(strlen(p)+2), SEEK_END);
1452     else fseek(tf, -(strlen(p)+3), SEEK_END);
1453     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1454     DisplayMessage(buf, "");
1455     free(p); appData.results = q;
1456     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1457        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1458       int round = appData.defaultMatchGames * appData.tourneyType;
1459       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1460          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1461         UnloadEngine(&first);  // next game belongs to other pairing;
1462         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1463     }
1464     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1465 }
1466
1467 void
1468 MatchEvent (int mode)
1469 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1470         int dummy;
1471         if(matchMode) { // already in match mode: switch it off
1472             abortMatch = TRUE;
1473             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1474             return;
1475         }
1476 //      if(gameMode != BeginningOfGame) {
1477 //          DisplayError(_("You can only start a match from the initial position."), 0);
1478 //          return;
1479 //      }
1480         abortMatch = FALSE;
1481         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1482         /* Set up machine vs. machine match */
1483         nextGame = 0;
1484         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1485         if(appData.tourneyFile[0]) {
1486             ReserveGame(-1, 0);
1487             if(nextGame > appData.matchGames) {
1488                 char buf[MSG_SIZ];
1489                 if(strchr(appData.results, '*') == NULL) {
1490                     FILE *f;
1491                     appData.tourneyCycles++;
1492                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1493                         fclose(f);
1494                         NextTourneyGame(-1, &dummy);
1495                         ReserveGame(-1, 0);
1496                         if(nextGame <= appData.matchGames) {
1497                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1498                             matchMode = mode;
1499                             ScheduleDelayedEvent(NextMatchGame, 10000);
1500                             return;
1501                         }
1502                     }
1503                 }
1504                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1505                 DisplayError(buf, 0);
1506                 appData.tourneyFile[0] = 0;
1507                 return;
1508             }
1509         } else
1510         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1511             DisplayFatalError(_("Can't have a match with no chess programs"),
1512                               0, 2);
1513             return;
1514         }
1515         matchMode = mode;
1516         matchGame = roundNr = 1;
1517         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1518         NextMatchGame();
1519 }
1520
1521 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1522
1523 void
1524 InitBackEnd3 P((void))
1525 {
1526     GameMode initialMode;
1527     char buf[MSG_SIZ];
1528     int err, len;
1529
1530     InitChessProgram(&first, startedFromSetupPosition);
1531
1532     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1533         free(programVersion);
1534         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1535         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1536         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1537     }
1538
1539     if (appData.icsActive) {
1540 #ifdef WIN32
1541         /* [DM] Make a console window if needed [HGM] merged ifs */
1542         ConsoleCreate();
1543 #endif
1544         err = establish();
1545         if (err != 0)
1546           {
1547             if (*appData.icsCommPort != NULLCHAR)
1548               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1549                              appData.icsCommPort);
1550             else
1551               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1552                         appData.icsHost, appData.icsPort);
1553
1554             if( (len >= MSG_SIZ) && appData.debugMode )
1555               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1556
1557             DisplayFatalError(buf, err, 1);
1558             return;
1559         }
1560         SetICSMode();
1561         telnetISR =
1562           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1563         fromUserISR =
1564           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1565         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1566             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1567     } else if (appData.noChessProgram) {
1568         SetNCPMode();
1569     } else {
1570         SetGNUMode();
1571     }
1572
1573     if (*appData.cmailGameName != NULLCHAR) {
1574         SetCmailMode();
1575         OpenLoopback(&cmailPR);
1576         cmailISR =
1577           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1578     }
1579
1580     ThawUI();
1581     DisplayMessage("", "");
1582     if (StrCaseCmp(appData.initialMode, "") == 0) {
1583       initialMode = BeginningOfGame;
1584       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1585         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1586         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1587         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1588         ModeHighlight();
1589       }
1590     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1591       initialMode = TwoMachinesPlay;
1592     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1593       initialMode = AnalyzeFile;
1594     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1595       initialMode = AnalyzeMode;
1596     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1597       initialMode = MachinePlaysWhite;
1598     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1599       initialMode = MachinePlaysBlack;
1600     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1601       initialMode = EditGame;
1602     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1603       initialMode = EditPosition;
1604     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1605       initialMode = Training;
1606     } else {
1607       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1608       if( (len >= MSG_SIZ) && appData.debugMode )
1609         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1610
1611       DisplayFatalError(buf, 0, 2);
1612       return;
1613     }
1614
1615     if (appData.matchMode) {
1616         if(appData.tourneyFile[0]) { // start tourney from command line
1617             FILE *f;
1618             if(f = fopen(appData.tourneyFile, "r")) {
1619                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1620                 fclose(f);
1621                 appData.clockMode = TRUE;
1622                 SetGNUMode();
1623             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1624         }
1625         MatchEvent(TRUE);
1626     } else if (*appData.cmailGameName != NULLCHAR) {
1627         /* Set up cmail mode */
1628         ReloadCmailMsgEvent(TRUE);
1629     } else {
1630         /* Set up other modes */
1631         if (initialMode == AnalyzeFile) {
1632           if (*appData.loadGameFile == NULLCHAR) {
1633             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1634             return;
1635           }
1636         }
1637         if (*appData.loadGameFile != NULLCHAR) {
1638             (void) LoadGameFromFile(appData.loadGameFile,
1639                                     appData.loadGameIndex,
1640                                     appData.loadGameFile, TRUE);
1641         } else if (*appData.loadPositionFile != NULLCHAR) {
1642             (void) LoadPositionFromFile(appData.loadPositionFile,
1643                                         appData.loadPositionIndex,
1644                                         appData.loadPositionFile);
1645             /* [HGM] try to make self-starting even after FEN load */
1646             /* to allow automatic setup of fairy variants with wtm */
1647             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1648                 gameMode = BeginningOfGame;
1649                 setboardSpoiledMachineBlack = 1;
1650             }
1651             /* [HGM] loadPos: make that every new game uses the setup */
1652             /* from file as long as we do not switch variant          */
1653             if(!blackPlaysFirst) {
1654                 startedFromPositionFile = TRUE;
1655                 CopyBoard(filePosition, boards[0]);
1656             }
1657         }
1658         if (initialMode == AnalyzeMode) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1661             return;
1662           }
1663           if (appData.icsActive) {
1664             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1665             return;
1666           }
1667           AnalyzeModeEvent();
1668         } else if (initialMode == AnalyzeFile) {
1669           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1670           ShowThinkingEvent();
1671           AnalyzeFileEvent();
1672           AnalysisPeriodicEvent(1);
1673         } else if (initialMode == MachinePlaysWhite) {
1674           if (appData.noChessProgram) {
1675             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1676                               0, 2);
1677             return;
1678           }
1679           if (appData.icsActive) {
1680             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1681                               0, 2);
1682             return;
1683           }
1684           MachineWhiteEvent();
1685         } else if (initialMode == MachinePlaysBlack) {
1686           if (appData.noChessProgram) {
1687             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1688                               0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1693                               0, 2);
1694             return;
1695           }
1696           MachineBlackEvent();
1697         } else if (initialMode == TwoMachinesPlay) {
1698           if (appData.noChessProgram) {
1699             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1700                               0, 2);
1701             return;
1702           }
1703           if (appData.icsActive) {
1704             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1705                               0, 2);
1706             return;
1707           }
1708           TwoMachinesEvent();
1709         } else if (initialMode == EditGame) {
1710           EditGameEvent();
1711         } else if (initialMode == EditPosition) {
1712           EditPositionEvent();
1713         } else if (initialMode == Training) {
1714           if (*appData.loadGameFile == NULLCHAR) {
1715             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1716             return;
1717           }
1718           TrainingEvent();
1719         }
1720     }
1721 }
1722
1723 void
1724 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1725 {
1726     DisplayBook(current+1);
1727
1728     MoveHistorySet( movelist, first, last, current, pvInfoList );
1729
1730     EvalGraphSet( first, last, current, pvInfoList );
1731
1732     MakeEngineOutputTitle();
1733 }
1734
1735 /*
1736  * Establish will establish a contact to a remote host.port.
1737  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1738  *  used to talk to the host.
1739  * Returns 0 if okay, error code if not.
1740  */
1741 int
1742 establish ()
1743 {
1744     char buf[MSG_SIZ];
1745
1746     if (*appData.icsCommPort != NULLCHAR) {
1747         /* Talk to the host through a serial comm port */
1748         return OpenCommPort(appData.icsCommPort, &icsPR);
1749
1750     } else if (*appData.gateway != NULLCHAR) {
1751         if (*appData.remoteShell == NULLCHAR) {
1752             /* Use the rcmd protocol to run telnet program on a gateway host */
1753             snprintf(buf, sizeof(buf), "%s %s %s",
1754                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1755             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1756
1757         } else {
1758             /* Use the rsh program to run telnet program on a gateway host */
1759             if (*appData.remoteUser == NULLCHAR) {
1760                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1761                         appData.gateway, appData.telnetProgram,
1762                         appData.icsHost, appData.icsPort);
1763             } else {
1764                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1765                         appData.remoteShell, appData.gateway,
1766                         appData.remoteUser, appData.telnetProgram,
1767                         appData.icsHost, appData.icsPort);
1768             }
1769             return StartChildProcess(buf, "", &icsPR);
1770
1771         }
1772     } else if (appData.useTelnet) {
1773         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1774
1775     } else {
1776         /* TCP socket interface differs somewhat between
1777            Unix and NT; handle details in the front end.
1778            */
1779         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1780     }
1781 }
1782
1783 void
1784 EscapeExpand (char *p, char *q)
1785 {       // [HGM] initstring: routine to shape up string arguments
1786         while(*p++ = *q++) if(p[-1] == '\\')
1787             switch(*q++) {
1788                 case 'n': p[-1] = '\n'; break;
1789                 case 'r': p[-1] = '\r'; break;
1790                 case 't': p[-1] = '\t'; break;
1791                 case '\\': p[-1] = '\\'; break;
1792                 case 0: *p = 0; return;
1793                 default: p[-1] = q[-1]; break;
1794             }
1795 }
1796
1797 void
1798 show_bytes (FILE *fp, char *buf, int count)
1799 {
1800     while (count--) {
1801         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1802             fprintf(fp, "\\%03o", *buf & 0xff);
1803         } else {
1804             putc(*buf, fp);
1805         }
1806         buf++;
1807     }
1808     fflush(fp);
1809 }
1810
1811 /* Returns an errno value */
1812 int
1813 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1814 {
1815     char buf[8192], *p, *q, *buflim;
1816     int left, newcount, outcount;
1817
1818     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1819         *appData.gateway != NULLCHAR) {
1820         if (appData.debugMode) {
1821             fprintf(debugFP, ">ICS: ");
1822             show_bytes(debugFP, message, count);
1823             fprintf(debugFP, "\n");
1824         }
1825         return OutputToProcess(pr, message, count, outError);
1826     }
1827
1828     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1829     p = message;
1830     q = buf;
1831     left = count;
1832     newcount = 0;
1833     while (left) {
1834         if (q >= buflim) {
1835             if (appData.debugMode) {
1836                 fprintf(debugFP, ">ICS: ");
1837                 show_bytes(debugFP, buf, newcount);
1838                 fprintf(debugFP, "\n");
1839             }
1840             outcount = OutputToProcess(pr, buf, newcount, outError);
1841             if (outcount < newcount) return -1; /* to be sure */
1842             q = buf;
1843             newcount = 0;
1844         }
1845         if (*p == '\n') {
1846             *q++ = '\r';
1847             newcount++;
1848         } else if (((unsigned char) *p) == TN_IAC) {
1849             *q++ = (char) TN_IAC;
1850             newcount ++;
1851         }
1852         *q++ = *p++;
1853         newcount++;
1854         left--;
1855     }
1856     if (appData.debugMode) {
1857         fprintf(debugFP, ">ICS: ");
1858         show_bytes(debugFP, buf, newcount);
1859         fprintf(debugFP, "\n");
1860     }
1861     outcount = OutputToProcess(pr, buf, newcount, outError);
1862     if (outcount < newcount) return -1; /* to be sure */
1863     return count;
1864 }
1865
1866 void
1867 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1868 {
1869     int outError, outCount;
1870     static int gotEof = 0;
1871     static FILE *ini;
1872
1873     /* Pass data read from player on to ICS */
1874     if (count > 0) {
1875         gotEof = 0;
1876         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1877         if (outCount < count) {
1878             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879         }
1880         if(have_sent_ICS_logon == 2) {
1881           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1882             fprintf(ini, "%s", message);
1883             have_sent_ICS_logon = 3;
1884           } else
1885             have_sent_ICS_logon = 1;
1886         } else if(have_sent_ICS_logon == 3) {
1887             fprintf(ini, "%s", message);
1888             fclose(ini);
1889           have_sent_ICS_logon = 1;
1890         }
1891     } else if (count < 0) {
1892         RemoveInputSource(isr);
1893         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1894     } else if (gotEof++ > 0) {
1895         RemoveInputSource(isr);
1896         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1897     }
1898 }
1899
1900 void
1901 KeepAlive ()
1902 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1903     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1904     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1905     SendToICS("date\n");
1906     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1907 }
1908
1909 /* added routine for printf style output to ics */
1910 void
1911 ics_printf (char *format, ...)
1912 {
1913     char buffer[MSG_SIZ];
1914     va_list args;
1915
1916     va_start(args, format);
1917     vsnprintf(buffer, sizeof(buffer), format, args);
1918     buffer[sizeof(buffer)-1] = '\0';
1919     SendToICS(buffer);
1920     va_end(args);
1921 }
1922
1923 void
1924 SendToICS (char *s)
1925 {
1926     int count, outCount, outError;
1927
1928     if (icsPR == NoProc) return;
1929
1930     count = strlen(s);
1931     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1932     if (outCount < count) {
1933         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1934     }
1935 }
1936
1937 /* This is used for sending logon scripts to the ICS. Sending
1938    without a delay causes problems when using timestamp on ICC
1939    (at least on my machine). */
1940 void
1941 SendToICSDelayed (char *s, long msdelay)
1942 {
1943     int count, outCount, outError;
1944
1945     if (icsPR == NoProc) return;
1946
1947     count = strlen(s);
1948     if (appData.debugMode) {
1949         fprintf(debugFP, ">ICS: ");
1950         show_bytes(debugFP, s, count);
1951         fprintf(debugFP, "\n");
1952     }
1953     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1954                                       msdelay);
1955     if (outCount < count) {
1956         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1957     }
1958 }
1959
1960
1961 /* Remove all highlighting escape sequences in s
1962    Also deletes any suffix starting with '('
1963    */
1964 char *
1965 StripHighlightAndTitle (char *s)
1966 {
1967     static char retbuf[MSG_SIZ];
1968     char *p = retbuf;
1969
1970     while (*s != NULLCHAR) {
1971         while (*s == '\033') {
1972             while (*s != NULLCHAR && !isalpha(*s)) s++;
1973             if (*s != NULLCHAR) s++;
1974         }
1975         while (*s != NULLCHAR && *s != '\033') {
1976             if (*s == '(' || *s == '[') {
1977                 *p = NULLCHAR;
1978                 return retbuf;
1979             }
1980             *p++ = *s++;
1981         }
1982     }
1983     *p = NULLCHAR;
1984     return retbuf;
1985 }
1986
1987 /* Remove all highlighting escape sequences in s */
1988 char *
1989 StripHighlight (char *s)
1990 {
1991     static char retbuf[MSG_SIZ];
1992     char *p = retbuf;
1993
1994     while (*s != NULLCHAR) {
1995         while (*s == '\033') {
1996             while (*s != NULLCHAR && !isalpha(*s)) s++;
1997             if (*s != NULLCHAR) s++;
1998         }
1999         while (*s != NULLCHAR && *s != '\033') {
2000             *p++ = *s++;
2001         }
2002     }
2003     *p = NULLCHAR;
2004     return retbuf;
2005 }
2006
2007 char *variantNames[] = VARIANT_NAMES;
2008 char *
2009 VariantName (VariantClass v)
2010 {
2011     return variantNames[v];
2012 }
2013
2014
2015 /* Identify a variant from the strings the chess servers use or the
2016    PGN Variant tag names we use. */
2017 VariantClass
2018 StringToVariant (char *e)
2019 {
2020     char *p;
2021     int wnum = -1;
2022     VariantClass v = VariantNormal;
2023     int i, found = FALSE;
2024     char buf[MSG_SIZ];
2025     int len;
2026
2027     if (!e) return v;
2028
2029     /* [HGM] skip over optional board-size prefixes */
2030     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2031         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2032         while( *e++ != '_');
2033     }
2034
2035     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2036         v = VariantNormal;
2037         found = TRUE;
2038     } else
2039     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2040       if (StrCaseStr(e, variantNames[i])) {
2041         v = (VariantClass) i;
2042         found = TRUE;
2043         break;
2044       }
2045     }
2046
2047     if (!found) {
2048       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2049           || StrCaseStr(e, "wild/fr")
2050           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2051         v = VariantFischeRandom;
2052       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2053                  (i = 1, p = StrCaseStr(e, "w"))) {
2054         p += i;
2055         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2056         if (isdigit(*p)) {
2057           wnum = atoi(p);
2058         } else {
2059           wnum = -1;
2060         }
2061         switch (wnum) {
2062         case 0: /* FICS only, actually */
2063         case 1:
2064           /* Castling legal even if K starts on d-file */
2065           v = VariantWildCastle;
2066           break;
2067         case 2:
2068         case 3:
2069         case 4:
2070           /* Castling illegal even if K & R happen to start in
2071              normal positions. */
2072           v = VariantNoCastle;
2073           break;
2074         case 5:
2075         case 7:
2076         case 8:
2077         case 10:
2078         case 11:
2079         case 12:
2080         case 13:
2081         case 14:
2082         case 15:
2083         case 18:
2084         case 19:
2085           /* Castling legal iff K & R start in normal positions */
2086           v = VariantNormal;
2087           break;
2088         case 6:
2089         case 20:
2090         case 21:
2091           /* Special wilds for position setup; unclear what to do here */
2092           v = VariantLoadable;
2093           break;
2094         case 9:
2095           /* Bizarre ICC game */
2096           v = VariantTwoKings;
2097           break;
2098         case 16:
2099           v = VariantKriegspiel;
2100           break;
2101         case 17:
2102           v = VariantLosers;
2103           break;
2104         case 22:
2105           v = VariantFischeRandom;
2106           break;
2107         case 23:
2108           v = VariantCrazyhouse;
2109           break;
2110         case 24:
2111           v = VariantBughouse;
2112           break;
2113         case 25:
2114           v = Variant3Check;
2115           break;
2116         case 26:
2117           /* Not quite the same as FICS suicide! */
2118           v = VariantGiveaway;
2119           break;
2120         case 27:
2121           v = VariantAtomic;
2122           break;
2123         case 28:
2124           v = VariantShatranj;
2125           break;
2126
2127         /* Temporary names for future ICC types.  The name *will* change in
2128            the next xboard/WinBoard release after ICC defines it. */
2129         case 29:
2130           v = Variant29;
2131           break;
2132         case 30:
2133           v = Variant30;
2134           break;
2135         case 31:
2136           v = Variant31;
2137           break;
2138         case 32:
2139           v = Variant32;
2140           break;
2141         case 33:
2142           v = Variant33;
2143           break;
2144         case 34:
2145           v = Variant34;
2146           break;
2147         case 35:
2148           v = Variant35;
2149           break;
2150         case 36:
2151           v = Variant36;
2152           break;
2153         case 37:
2154           v = VariantShogi;
2155           break;
2156         case 38:
2157           v = VariantXiangqi;
2158           break;
2159         case 39:
2160           v = VariantCourier;
2161           break;
2162         case 40:
2163           v = VariantGothic;
2164           break;
2165         case 41:
2166           v = VariantCapablanca;
2167           break;
2168         case 42:
2169           v = VariantKnightmate;
2170           break;
2171         case 43:
2172           v = VariantFairy;
2173           break;
2174         case 44:
2175           v = VariantCylinder;
2176           break;
2177         case 45:
2178           v = VariantFalcon;
2179           break;
2180         case 46:
2181           v = VariantCapaRandom;
2182           break;
2183         case 47:
2184           v = VariantBerolina;
2185           break;
2186         case 48:
2187           v = VariantJanus;
2188           break;
2189         case 49:
2190           v = VariantSuper;
2191           break;
2192         case 50:
2193           v = VariantGreat;
2194           break;
2195         case -1:
2196           /* Found "wild" or "w" in the string but no number;
2197              must assume it's normal chess. */
2198           v = VariantNormal;
2199           break;
2200         default:
2201           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2202           if( (len >= MSG_SIZ) && appData.debugMode )
2203             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2204
2205           DisplayError(buf, 0);
2206           v = VariantUnknown;
2207           break;
2208         }
2209       }
2210     }
2211     if (appData.debugMode) {
2212       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2213               e, wnum, VariantName(v));
2214     }
2215     return v;
2216 }
2217
2218 static int leftover_start = 0, leftover_len = 0;
2219 char star_match[STAR_MATCH_N][MSG_SIZ];
2220
2221 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2222    advance *index beyond it, and set leftover_start to the new value of
2223    *index; else return FALSE.  If pattern contains the character '*', it
2224    matches any sequence of characters not containing '\r', '\n', or the
2225    character following the '*' (if any), and the matched sequence(s) are
2226    copied into star_match.
2227    */
2228 int
2229 looking_at ( char *buf, int *index, char *pattern)
2230 {
2231     char *bufp = &buf[*index], *patternp = pattern;
2232     int star_count = 0;
2233     char *matchp = star_match[0];
2234
2235     for (;;) {
2236         if (*patternp == NULLCHAR) {
2237             *index = leftover_start = bufp - buf;
2238             *matchp = NULLCHAR;
2239             return TRUE;
2240         }
2241         if (*bufp == NULLCHAR) return FALSE;
2242         if (*patternp == '*') {
2243             if (*bufp == *(patternp + 1)) {
2244                 *matchp = NULLCHAR;
2245                 matchp = star_match[++star_count];
2246                 patternp += 2;
2247                 bufp++;
2248                 continue;
2249             } else if (*bufp == '\n' || *bufp == '\r') {
2250                 patternp++;
2251                 if (*patternp == NULLCHAR)
2252                   continue;
2253                 else
2254                   return FALSE;
2255             } else {
2256                 *matchp++ = *bufp++;
2257                 continue;
2258             }
2259         }
2260         if (*patternp != *bufp) return FALSE;
2261         patternp++;
2262         bufp++;
2263     }
2264 }
2265
2266 void
2267 SendToPlayer (char *data, int length)
2268 {
2269     int error, outCount;
2270     outCount = OutputToProcess(NoProc, data, length, &error);
2271     if (outCount < length) {
2272         DisplayFatalError(_("Error writing to display"), error, 1);
2273     }
2274 }
2275
2276 void
2277 PackHolding (char packed[], char *holding)
2278 {
2279     char *p = holding;
2280     char *q = packed;
2281     int runlength = 0;
2282     int curr = 9999;
2283     do {
2284         if (*p == curr) {
2285             runlength++;
2286         } else {
2287             switch (runlength) {
2288               case 0:
2289                 break;
2290               case 1:
2291                 *q++ = curr;
2292                 break;
2293               case 2:
2294                 *q++ = curr;
2295                 *q++ = curr;
2296                 break;
2297               default:
2298                 sprintf(q, "%d", runlength);
2299                 while (*q) q++;
2300                 *q++ = curr;
2301                 break;
2302             }
2303             runlength = 1;
2304             curr = *p;
2305         }
2306     } while (*p++);
2307     *q = NULLCHAR;
2308 }
2309
2310 /* Telnet protocol requests from the front end */
2311 void
2312 TelnetRequest (unsigned char ddww, unsigned char option)
2313 {
2314     unsigned char msg[3];
2315     int outCount, outError;
2316
2317     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2318
2319     if (appData.debugMode) {
2320         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2321         switch (ddww) {
2322           case TN_DO:
2323             ddwwStr = "DO";
2324             break;
2325           case TN_DONT:
2326             ddwwStr = "DONT";
2327             break;
2328           case TN_WILL:
2329             ddwwStr = "WILL";
2330             break;
2331           case TN_WONT:
2332             ddwwStr = "WONT";
2333             break;
2334           default:
2335             ddwwStr = buf1;
2336             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2337             break;
2338         }
2339         switch (option) {
2340           case TN_ECHO:
2341             optionStr = "ECHO";
2342             break;
2343           default:
2344             optionStr = buf2;
2345             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2346             break;
2347         }
2348         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2349     }
2350     msg[0] = TN_IAC;
2351     msg[1] = ddww;
2352     msg[2] = option;
2353     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2354     if (outCount < 3) {
2355         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2356     }
2357 }
2358
2359 void
2360 DoEcho ()
2361 {
2362     if (!appData.icsActive) return;
2363     TelnetRequest(TN_DO, TN_ECHO);
2364 }
2365
2366 void
2367 DontEcho ()
2368 {
2369     if (!appData.icsActive) return;
2370     TelnetRequest(TN_DONT, TN_ECHO);
2371 }
2372
2373 void
2374 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2375 {
2376     /* put the holdings sent to us by the server on the board holdings area */
2377     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2378     char p;
2379     ChessSquare piece;
2380
2381     if(gameInfo.holdingsWidth < 2)  return;
2382     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2383         return; // prevent overwriting by pre-board holdings
2384
2385     if( (int)lowestPiece >= BlackPawn ) {
2386         holdingsColumn = 0;
2387         countsColumn = 1;
2388         holdingsStartRow = BOARD_HEIGHT-1;
2389         direction = -1;
2390     } else {
2391         holdingsColumn = BOARD_WIDTH-1;
2392         countsColumn = BOARD_WIDTH-2;
2393         holdingsStartRow = 0;
2394         direction = 1;
2395     }
2396
2397     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2398         board[i][holdingsColumn] = EmptySquare;
2399         board[i][countsColumn]   = (ChessSquare) 0;
2400     }
2401     while( (p=*holdings++) != NULLCHAR ) {
2402         piece = CharToPiece( ToUpper(p) );
2403         if(piece == EmptySquare) continue;
2404         /*j = (int) piece - (int) WhitePawn;*/
2405         j = PieceToNumber(piece);
2406         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2407         if(j < 0) continue;               /* should not happen */
2408         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2409         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2410         board[holdingsStartRow+j*direction][countsColumn]++;
2411     }
2412 }
2413
2414
2415 void
2416 VariantSwitch (Board board, VariantClass newVariant)
2417 {
2418    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2419    static Board oldBoard;
2420
2421    startedFromPositionFile = FALSE;
2422    if(gameInfo.variant == newVariant) return;
2423
2424    /* [HGM] This routine is called each time an assignment is made to
2425     * gameInfo.variant during a game, to make sure the board sizes
2426     * are set to match the new variant. If that means adding or deleting
2427     * holdings, we shift the playing board accordingly
2428     * This kludge is needed because in ICS observe mode, we get boards
2429     * of an ongoing game without knowing the variant, and learn about the
2430     * latter only later. This can be because of the move list we requested,
2431     * in which case the game history is refilled from the beginning anyway,
2432     * but also when receiving holdings of a crazyhouse game. In the latter
2433     * case we want to add those holdings to the already received position.
2434     */
2435
2436
2437    if (appData.debugMode) {
2438      fprintf(debugFP, "Switch board from %s to %s\n",
2439              VariantName(gameInfo.variant), VariantName(newVariant));
2440      setbuf(debugFP, NULL);
2441    }
2442    shuffleOpenings = 0;       /* [HGM] shuffle */
2443    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2444    switch(newVariant)
2445      {
2446      case VariantShogi:
2447        newWidth = 9;  newHeight = 9;
2448        gameInfo.holdingsSize = 7;
2449      case VariantBughouse:
2450      case VariantCrazyhouse:
2451        newHoldingsWidth = 2; break;
2452      case VariantGreat:
2453        newWidth = 10;
2454      case VariantSuper:
2455        newHoldingsWidth = 2;
2456        gameInfo.holdingsSize = 8;
2457        break;
2458      case VariantGothic:
2459      case VariantCapablanca:
2460      case VariantCapaRandom:
2461        newWidth = 10;
2462      default:
2463        newHoldingsWidth = gameInfo.holdingsSize = 0;
2464      };
2465
2466    if(newWidth  != gameInfo.boardWidth  ||
2467       newHeight != gameInfo.boardHeight ||
2468       newHoldingsWidth != gameInfo.holdingsWidth ) {
2469
2470      /* shift position to new playing area, if needed */
2471      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2472        for(i=0; i<BOARD_HEIGHT; i++)
2473          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2474            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2475              board[i][j];
2476        for(i=0; i<newHeight; i++) {
2477          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2478          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2479        }
2480      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2481        for(i=0; i<BOARD_HEIGHT; i++)
2482          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2483            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2484              board[i][j];
2485      }
2486      board[HOLDINGS_SET] = 0;
2487      gameInfo.boardWidth  = newWidth;
2488      gameInfo.boardHeight = newHeight;
2489      gameInfo.holdingsWidth = newHoldingsWidth;
2490      gameInfo.variant = newVariant;
2491      InitDrawingSizes(-2, 0);
2492    } else gameInfo.variant = newVariant;
2493    CopyBoard(oldBoard, board);   // remember correctly formatted board
2494      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2495    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2496 }
2497
2498 static int loggedOn = FALSE;
2499
2500 /*-- Game start info cache: --*/
2501 int gs_gamenum;
2502 char gs_kind[MSG_SIZ];
2503 static char player1Name[128] = "";
2504 static char player2Name[128] = "";
2505 static char cont_seq[] = "\n\\   ";
2506 static int player1Rating = -1;
2507 static int player2Rating = -1;
2508 /*----------------------------*/
2509
2510 ColorClass curColor = ColorNormal;
2511 int suppressKibitz = 0;
2512
2513 // [HGM] seekgraph
2514 Boolean soughtPending = FALSE;
2515 Boolean seekGraphUp;
2516 #define MAX_SEEK_ADS 200
2517 #define SQUARE 0x80
2518 char *seekAdList[MAX_SEEK_ADS];
2519 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2520 float tcList[MAX_SEEK_ADS];
2521 char colorList[MAX_SEEK_ADS];
2522 int nrOfSeekAds = 0;
2523 int minRating = 1010, maxRating = 2800;
2524 int hMargin = 10, vMargin = 20, h, w;
2525 extern int squareSize, lineGap;
2526
2527 void
2528 PlotSeekAd (int i)
2529 {
2530         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2531         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2532         if(r < minRating+100 && r >=0 ) r = minRating+100;
2533         if(r > maxRating) r = maxRating;
2534         if(tc < 1.f) tc = 1.f;
2535         if(tc > 95.f) tc = 95.f;
2536         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2537         y = ((double)r - minRating)/(maxRating - minRating)
2538             * (h-vMargin-squareSize/8-1) + vMargin;
2539         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2540         if(strstr(seekAdList[i], " u ")) color = 1;
2541         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2542            !strstr(seekAdList[i], "bullet") &&
2543            !strstr(seekAdList[i], "blitz") &&
2544            !strstr(seekAdList[i], "standard") ) color = 2;
2545         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2546         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2547 }
2548
2549 void
2550 PlotSingleSeekAd (int i)
2551 {
2552         PlotSeekAd(i);
2553 }
2554
2555 void
2556 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2557 {
2558         char buf[MSG_SIZ], *ext = "";
2559         VariantClass v = StringToVariant(type);
2560         if(strstr(type, "wild")) {
2561             ext = type + 4; // append wild number
2562             if(v == VariantFischeRandom) type = "chess960"; else
2563             if(v == VariantLoadable) type = "setup"; else
2564             type = VariantName(v);
2565         }
2566         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2567         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2568             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2569             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2570             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2571             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2572             seekNrList[nrOfSeekAds] = nr;
2573             zList[nrOfSeekAds] = 0;
2574             seekAdList[nrOfSeekAds++] = StrSave(buf);
2575             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2576         }
2577 }
2578
2579 void
2580 EraseSeekDot (int i)
2581 {
2582     int x = xList[i], y = yList[i], d=squareSize/4, k;
2583     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2584     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2585     // now replot every dot that overlapped
2586     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2587         int xx = xList[k], yy = yList[k];
2588         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2589             DrawSeekDot(xx, yy, colorList[k]);
2590     }
2591 }
2592
2593 void
2594 RemoveSeekAd (int nr)
2595 {
2596         int i;
2597         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2598             EraseSeekDot(i);
2599             if(seekAdList[i]) free(seekAdList[i]);
2600             seekAdList[i] = seekAdList[--nrOfSeekAds];
2601             seekNrList[i] = seekNrList[nrOfSeekAds];
2602             ratingList[i] = ratingList[nrOfSeekAds];
2603             colorList[i]  = colorList[nrOfSeekAds];
2604             tcList[i] = tcList[nrOfSeekAds];
2605             xList[i]  = xList[nrOfSeekAds];
2606             yList[i]  = yList[nrOfSeekAds];
2607             zList[i]  = zList[nrOfSeekAds];
2608             seekAdList[nrOfSeekAds] = NULL;
2609             break;
2610         }
2611 }
2612
2613 Boolean
2614 MatchSoughtLine (char *line)
2615 {
2616     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2617     int nr, base, inc, u=0; char dummy;
2618
2619     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2620        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2621        (u=1) &&
2622        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2623         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2624         // match: compact and save the line
2625         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2626         return TRUE;
2627     }
2628     return FALSE;
2629 }
2630
2631 int
2632 DrawSeekGraph ()
2633 {
2634     int i;
2635     if(!seekGraphUp) return FALSE;
2636     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2637     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2638
2639     DrawSeekBackground(0, 0, w, h);
2640     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2641     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2642     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2643         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2644         yy = h-1-yy;
2645         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2646         if(i%500 == 0) {
2647             char buf[MSG_SIZ];
2648             snprintf(buf, MSG_SIZ, "%d", i);
2649             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2650         }
2651     }
2652     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2653     for(i=1; i<100; i+=(i<10?1:5)) {
2654         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2655         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2656         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2657             char buf[MSG_SIZ];
2658             snprintf(buf, MSG_SIZ, "%d", i);
2659             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2660         }
2661     }
2662     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2663     return TRUE;
2664 }
2665
2666 int
2667 SeekGraphClick (ClickType click, int x, int y, int moving)
2668 {
2669     static int lastDown = 0, displayed = 0, lastSecond;
2670     if(y < 0) return FALSE;
2671     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2672         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2673         if(!seekGraphUp) return FALSE;
2674         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2675         DrawPosition(TRUE, NULL);
2676         return TRUE;
2677     }
2678     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2679         if(click == Release || moving) return FALSE;
2680         nrOfSeekAds = 0;
2681         soughtPending = TRUE;
2682         SendToICS(ics_prefix);
2683         SendToICS("sought\n"); // should this be "sought all"?
2684     } else { // issue challenge based on clicked ad
2685         int dist = 10000; int i, closest = 0, second = 0;
2686         for(i=0; i<nrOfSeekAds; i++) {
2687             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2688             if(d < dist) { dist = d; closest = i; }
2689             second += (d - zList[i] < 120); // count in-range ads
2690             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2691         }
2692         if(dist < 120) {
2693             char buf[MSG_SIZ];
2694             second = (second > 1);
2695             if(displayed != closest || second != lastSecond) {
2696                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2697                 lastSecond = second; displayed = closest;
2698             }
2699             if(click == Press) {
2700                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2701                 lastDown = closest;
2702                 return TRUE;
2703             } // on press 'hit', only show info
2704             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2705             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2706             SendToICS(ics_prefix);
2707             SendToICS(buf);
2708             return TRUE; // let incoming board of started game pop down the graph
2709         } else if(click == Release) { // release 'miss' is ignored
2710             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2711             if(moving == 2) { // right up-click
2712                 nrOfSeekAds = 0; // refresh graph
2713                 soughtPending = TRUE;
2714                 SendToICS(ics_prefix);
2715                 SendToICS("sought\n"); // should this be "sought all"?
2716             }
2717             return TRUE;
2718         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2719         // press miss or release hit 'pop down' seek graph
2720         seekGraphUp = FALSE;
2721         DrawPosition(TRUE, NULL);
2722     }
2723     return TRUE;
2724 }
2725
2726 void
2727 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2728 {
2729 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2730 #define STARTED_NONE 0
2731 #define STARTED_MOVES 1
2732 #define STARTED_BOARD 2
2733 #define STARTED_OBSERVE 3
2734 #define STARTED_HOLDINGS 4
2735 #define STARTED_CHATTER 5
2736 #define STARTED_COMMENT 6
2737 #define STARTED_MOVES_NOHIDE 7
2738
2739     static int started = STARTED_NONE;
2740     static char parse[20000];
2741     static int parse_pos = 0;
2742     static char buf[BUF_SIZE + 1];
2743     static int firstTime = TRUE, intfSet = FALSE;
2744     static ColorClass prevColor = ColorNormal;
2745     static int savingComment = FALSE;
2746     static int cmatch = 0; // continuation sequence match
2747     char *bp;
2748     char str[MSG_SIZ];
2749     int i, oldi;
2750     int buf_len;
2751     int next_out;
2752     int tkind;
2753     int backup;    /* [DM] For zippy color lines */
2754     char *p;
2755     char talker[MSG_SIZ]; // [HGM] chat
2756     int channel;
2757
2758     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2759
2760     if (appData.debugMode) {
2761       if (!error) {
2762         fprintf(debugFP, "<ICS: ");
2763         show_bytes(debugFP, data, count);
2764         fprintf(debugFP, "\n");
2765       }
2766     }
2767
2768     if (appData.debugMode) { int f = forwardMostMove;
2769         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2770                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2771                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2772     }
2773     if (count > 0) {
2774         /* If last read ended with a partial line that we couldn't parse,
2775            prepend it to the new read and try again. */
2776         if (leftover_len > 0) {
2777             for (i=0; i<leftover_len; i++)
2778               buf[i] = buf[leftover_start + i];
2779         }
2780
2781     /* copy new characters into the buffer */
2782     bp = buf + leftover_len;
2783     buf_len=leftover_len;
2784     for (i=0; i<count; i++)
2785     {
2786         // ignore these
2787         if (data[i] == '\r')
2788             continue;
2789
2790         // join lines split by ICS?
2791         if (!appData.noJoin)
2792         {
2793             /*
2794                 Joining just consists of finding matches against the
2795                 continuation sequence, and discarding that sequence
2796                 if found instead of copying it.  So, until a match
2797                 fails, there's nothing to do since it might be the
2798                 complete sequence, and thus, something we don't want
2799                 copied.
2800             */
2801             if (data[i] == cont_seq[cmatch])
2802             {
2803                 cmatch++;
2804                 if (cmatch == strlen(cont_seq))
2805                 {
2806                     cmatch = 0; // complete match.  just reset the counter
2807
2808                     /*
2809                         it's possible for the ICS to not include the space
2810                         at the end of the last word, making our [correct]
2811                         join operation fuse two separate words.  the server
2812                         does this when the space occurs at the width setting.
2813                     */
2814                     if (!buf_len || buf[buf_len-1] != ' ')
2815                     {
2816                         *bp++ = ' ';
2817                         buf_len++;
2818                     }
2819                 }
2820                 continue;
2821             }
2822             else if (cmatch)
2823             {
2824                 /*
2825                     match failed, so we have to copy what matched before
2826                     falling through and copying this character.  In reality,
2827                     this will only ever be just the newline character, but
2828                     it doesn't hurt to be precise.
2829                 */
2830                 strncpy(bp, cont_seq, cmatch);
2831                 bp += cmatch;
2832                 buf_len += cmatch;
2833                 cmatch = 0;
2834             }
2835         }
2836
2837         // copy this char
2838         *bp++ = data[i];
2839         buf_len++;
2840     }
2841
2842         buf[buf_len] = NULLCHAR;
2843 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2844         next_out = 0;
2845         leftover_start = 0;
2846
2847         i = 0;
2848         while (i < buf_len) {
2849             /* Deal with part of the TELNET option negotiation
2850                protocol.  We refuse to do anything beyond the
2851                defaults, except that we allow the WILL ECHO option,
2852                which ICS uses to turn off password echoing when we are
2853                directly connected to it.  We reject this option
2854                if localLineEditing mode is on (always on in xboard)
2855                and we are talking to port 23, which might be a real
2856                telnet server that will try to keep WILL ECHO on permanently.
2857              */
2858             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2859                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2860                 unsigned char option;
2861                 oldi = i;
2862                 switch ((unsigned char) buf[++i]) {
2863                   case TN_WILL:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<WILL ");
2866                     switch (option = (unsigned char) buf[++i]) {
2867                       case TN_ECHO:
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "ECHO ");
2870                         /* Reply only if this is a change, according
2871                            to the protocol rules. */
2872                         if (remoteEchoOption) break;
2873                         if (appData.localLineEditing &&
2874                             atoi(appData.icsPort) == TN_PORT) {
2875                             TelnetRequest(TN_DONT, TN_ECHO);
2876                         } else {
2877                             EchoOff();
2878                             TelnetRequest(TN_DO, TN_ECHO);
2879                             remoteEchoOption = TRUE;
2880                         }
2881                         break;
2882                       default:
2883                         if (appData.debugMode)
2884                           fprintf(debugFP, "%d ", option);
2885                         /* Whatever this is, we don't want it. */
2886                         TelnetRequest(TN_DONT, option);
2887                         break;
2888                     }
2889                     break;
2890                   case TN_WONT:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<WONT ");
2893                     switch (option = (unsigned char) buf[++i]) {
2894                       case TN_ECHO:
2895                         if (appData.debugMode)
2896                           fprintf(debugFP, "ECHO ");
2897                         /* Reply only if this is a change, according
2898                            to the protocol rules. */
2899                         if (!remoteEchoOption) break;
2900                         EchoOn();
2901                         TelnetRequest(TN_DONT, TN_ECHO);
2902                         remoteEchoOption = FALSE;
2903                         break;
2904                       default:
2905                         if (appData.debugMode)
2906                           fprintf(debugFP, "%d ", (unsigned char) option);
2907                         /* Whatever this is, it must already be turned
2908                            off, because we never agree to turn on
2909                            anything non-default, so according to the
2910                            protocol rules, we don't reply. */
2911                         break;
2912                     }
2913                     break;
2914                   case TN_DO:
2915                     if (appData.debugMode)
2916                       fprintf(debugFP, "\n<DO ");
2917                     switch (option = (unsigned char) buf[++i]) {
2918                       default:
2919                         /* Whatever this is, we refuse to do it. */
2920                         if (appData.debugMode)
2921                           fprintf(debugFP, "%d ", option);
2922                         TelnetRequest(TN_WONT, option);
2923                         break;
2924                     }
2925                     break;
2926                   case TN_DONT:
2927                     if (appData.debugMode)
2928                       fprintf(debugFP, "\n<DONT ");
2929                     switch (option = (unsigned char) buf[++i]) {
2930                       default:
2931                         if (appData.debugMode)
2932                           fprintf(debugFP, "%d ", option);
2933                         /* Whatever this is, we are already not doing
2934                            it, because we never agree to do anything
2935                            non-default, so according to the protocol
2936                            rules, we don't reply. */
2937                         break;
2938                     }
2939                     break;
2940                   case TN_IAC:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<IAC ");
2943                     /* Doubled IAC; pass it through */
2944                     i--;
2945                     break;
2946                   default:
2947                     if (appData.debugMode)
2948                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2949                     /* Drop all other telnet commands on the floor */
2950                     break;
2951                 }
2952                 if (oldi > next_out)
2953                   SendToPlayer(&buf[next_out], oldi - next_out);
2954                 if (++i > next_out)
2955                   next_out = i;
2956                 continue;
2957             }
2958
2959             /* OK, this at least will *usually* work */
2960             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2961                 loggedOn = TRUE;
2962             }
2963
2964             if (loggedOn && !intfSet) {
2965                 if (ics_type == ICS_ICC) {
2966                   snprintf(str, MSG_SIZ,
2967                           "/set-quietly interface %s\n/set-quietly style 12\n",
2968                           programVersion);
2969                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2970                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2971                 } else if (ics_type == ICS_CHESSNET) {
2972                   snprintf(str, MSG_SIZ, "/style 12\n");
2973                 } else {
2974                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2975                   strcat(str, programVersion);
2976                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2977                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2978                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2979 #ifdef WIN32
2980                   strcat(str, "$iset nohighlight 1\n");
2981 #endif
2982                   strcat(str, "$iset lock 1\n$style 12\n");
2983                 }
2984                 SendToICS(str);
2985                 NotifyFrontendLogin();
2986                 intfSet = TRUE;
2987             }
2988
2989             if (started == STARTED_COMMENT) {
2990                 /* Accumulate characters in comment */
2991                 parse[parse_pos++] = buf[i];
2992                 if (buf[i] == '\n') {
2993                     parse[parse_pos] = NULLCHAR;
2994                     if(chattingPartner>=0) {
2995                         char mess[MSG_SIZ];
2996                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2997                         OutputChatMessage(chattingPartner, mess);
2998                         chattingPartner = -1;
2999                         next_out = i+1; // [HGM] suppress printing in ICS window
3000                     } else
3001                     if(!suppressKibitz) // [HGM] kibitz
3002                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3003                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3004                         int nrDigit = 0, nrAlph = 0, j;
3005                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3006                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3007                         parse[parse_pos] = NULLCHAR;
3008                         // try to be smart: if it does not look like search info, it should go to
3009                         // ICS interaction window after all, not to engine-output window.
3010                         for(j=0; j<parse_pos; j++) { // count letters and digits
3011                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3012                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3013                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3014                         }
3015                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3016                             int depth=0; float score;
3017                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3018                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3019                                 pvInfoList[forwardMostMove-1].depth = depth;
3020                                 pvInfoList[forwardMostMove-1].score = 100*score;
3021                             }
3022                             OutputKibitz(suppressKibitz, parse);
3023                         } else {
3024                             char tmp[MSG_SIZ];
3025                             if(gameMode == IcsObserving) // restore original ICS messages
3026                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3027                             else
3028                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3029                             SendToPlayer(tmp, strlen(tmp));
3030                         }
3031                         next_out = i+1; // [HGM] suppress printing in ICS window
3032                     }
3033                     started = STARTED_NONE;
3034                 } else {
3035                     /* Don't match patterns against characters in comment */
3036                     i++;
3037                     continue;
3038                 }
3039             }
3040             if (started == STARTED_CHATTER) {
3041                 if (buf[i] != '\n') {
3042                     /* Don't match patterns against characters in chatter */
3043                     i++;
3044                     continue;
3045                 }
3046                 started = STARTED_NONE;
3047                 if(suppressKibitz) next_out = i+1;
3048             }
3049
3050             /* Kludge to deal with rcmd protocol */
3051             if (firstTime && looking_at(buf, &i, "\001*")) {
3052                 DisplayFatalError(&buf[1], 0, 1);
3053                 continue;
3054             } else {
3055                 firstTime = FALSE;
3056             }
3057
3058             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3059                 ics_type = ICS_ICC;
3060                 ics_prefix = "/";
3061                 if (appData.debugMode)
3062                   fprintf(debugFP, "ics_type %d\n", ics_type);
3063                 continue;
3064             }
3065             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3066                 ics_type = ICS_FICS;
3067                 ics_prefix = "$";
3068                 if (appData.debugMode)
3069                   fprintf(debugFP, "ics_type %d\n", ics_type);
3070                 continue;
3071             }
3072             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3073                 ics_type = ICS_CHESSNET;
3074                 ics_prefix = "/";
3075                 if (appData.debugMode)
3076                   fprintf(debugFP, "ics_type %d\n", ics_type);
3077                 continue;
3078             }
3079
3080             if (!loggedOn &&
3081                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3082                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3083                  looking_at(buf, &i, "will be \"*\""))) {
3084               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3085               continue;
3086             }
3087
3088             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3089               char buf[MSG_SIZ];
3090               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3091               DisplayIcsInteractionTitle(buf);
3092               have_set_title = TRUE;
3093             }
3094
3095             /* skip finger notes */
3096             if (started == STARTED_NONE &&
3097                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3098                  (buf[i] == '1' && buf[i+1] == '0')) &&
3099                 buf[i+2] == ':' && buf[i+3] == ' ') {
3100               started = STARTED_CHATTER;
3101               i += 3;
3102               continue;
3103             }
3104
3105             oldi = i;
3106             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3107             if(appData.seekGraph) {
3108                 if(soughtPending && MatchSoughtLine(buf+i)) {
3109                     i = strstr(buf+i, "rated") - buf;
3110                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                     next_out = leftover_start = i;
3112                     started = STARTED_CHATTER;
3113                     suppressKibitz = TRUE;
3114                     continue;
3115                 }
3116                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3117                         && looking_at(buf, &i, "* ads displayed")) {
3118                     soughtPending = FALSE;
3119                     seekGraphUp = TRUE;
3120                     DrawSeekGraph();
3121                     continue;
3122                 }
3123                 if(appData.autoRefresh) {
3124                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3125                         int s = (ics_type == ICS_ICC); // ICC format differs
3126                         if(seekGraphUp)
3127                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3128                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3129                         looking_at(buf, &i, "*% "); // eat prompt
3130                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3131                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132                         next_out = i; // suppress
3133                         continue;
3134                     }
3135                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3136                         char *p = star_match[0];
3137                         while(*p) {
3138                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3139                             while(*p && *p++ != ' '); // next
3140                         }
3141                         looking_at(buf, &i, "*% "); // eat prompt
3142                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                         next_out = i;
3144                         continue;
3145                     }
3146                 }
3147             }
3148
3149             /* skip formula vars */
3150             if (started == STARTED_NONE &&
3151                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3152               started = STARTED_CHATTER;
3153               i += 3;
3154               continue;
3155             }
3156
3157             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3158             if (appData.autoKibitz && started == STARTED_NONE &&
3159                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3160                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3161                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3162                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3163                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3164                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3165                         suppressKibitz = TRUE;
3166                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167                         next_out = i;
3168                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3169                                 && (gameMode == IcsPlayingWhite)) ||
3170                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3171                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3172                             started = STARTED_CHATTER; // own kibitz we simply discard
3173                         else {
3174                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3175                             parse_pos = 0; parse[0] = NULLCHAR;
3176                             savingComment = TRUE;
3177                             suppressKibitz = gameMode != IcsObserving ? 2 :
3178                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3179                         }
3180                         continue;
3181                 } else
3182                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3183                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3184                          && atoi(star_match[0])) {
3185                     // suppress the acknowledgements of our own autoKibitz
3186                     char *p;
3187                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3188                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3189                     SendToPlayer(star_match[0], strlen(star_match[0]));
3190                     if(looking_at(buf, &i, "*% ")) // eat prompt
3191                         suppressKibitz = FALSE;
3192                     next_out = i;
3193                     continue;
3194                 }
3195             } // [HGM] kibitz: end of patch
3196
3197             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3198
3199             // [HGM] chat: intercept tells by users for which we have an open chat window
3200             channel = -1;
3201             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3202                                            looking_at(buf, &i, "* whispers:") ||
3203                                            looking_at(buf, &i, "* kibitzes:") ||
3204                                            looking_at(buf, &i, "* shouts:") ||
3205                                            looking_at(buf, &i, "* c-shouts:") ||
3206                                            looking_at(buf, &i, "--> * ") ||
3207                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3208                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3209                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3210                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3211                 int p;
3212                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3213                 chattingPartner = -1;
3214
3215                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3216                 for(p=0; p<MAX_CHAT; p++) {
3217                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3218                     talker[0] = '['; strcat(talker, "] ");
3219                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3220                     chattingPartner = p; break;
3221                     }
3222                 } else
3223                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3224                 for(p=0; p<MAX_CHAT; p++) {
3225                     if(!strcmp("kibitzes", chatPartner[p])) {
3226                         talker[0] = '['; strcat(talker, "] ");
3227                         chattingPartner = p; break;
3228                     }
3229                 } else
3230                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3231                 for(p=0; p<MAX_CHAT; p++) {
3232                     if(!strcmp("whispers", chatPartner[p])) {
3233                         talker[0] = '['; strcat(talker, "] ");
3234                         chattingPartner = p; break;
3235                     }
3236                 } else
3237                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3238                   if(buf[i-8] == '-' && buf[i-3] == 't')
3239                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3240                     if(!strcmp("c-shouts", chatPartner[p])) {
3241                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3242                         chattingPartner = p; break;
3243                     }
3244                   }
3245                   if(chattingPartner < 0)
3246                   for(p=0; p<MAX_CHAT; p++) {
3247                     if(!strcmp("shouts", chatPartner[p])) {
3248                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3249                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3250                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3251                         chattingPartner = p; break;
3252                     }
3253                   }
3254                 }
3255                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3256                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3257                     talker[0] = 0; Colorize(ColorTell, FALSE);
3258                     chattingPartner = p; break;
3259                 }
3260                 if(chattingPartner<0) i = oldi; else {
3261                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3262                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3263                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3264                     started = STARTED_COMMENT;
3265                     parse_pos = 0; parse[0] = NULLCHAR;
3266                     savingComment = 3 + chattingPartner; // counts as TRUE
3267                     suppressKibitz = TRUE;
3268                     continue;
3269                 }
3270             } // [HGM] chat: end of patch
3271
3272           backup = i;
3273             if (appData.zippyTalk || appData.zippyPlay) {
3274                 /* [DM] Backup address for color zippy lines */
3275 #if ZIPPY
3276                if (loggedOn == TRUE)
3277                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3278                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3279 #endif
3280             } // [DM] 'else { ' deleted
3281                 if (
3282                     /* Regular tells and says */
3283                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3284                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3285                     looking_at(buf, &i, "* says: ") ||
3286                     /* Don't color "message" or "messages" output */
3287                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3288                     looking_at(buf, &i, "*. * at *:*: ") ||
3289                     looking_at(buf, &i, "--* (*:*): ") ||
3290                     /* Message notifications (same color as tells) */
3291                     looking_at(buf, &i, "* has left a message ") ||
3292                     looking_at(buf, &i, "* just sent you a message:\n") ||
3293                     /* Whispers and kibitzes */
3294                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3295                     looking_at(buf, &i, "* kibitzes: ") ||
3296                     /* Channel tells */
3297                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3298
3299                   if (tkind == 1 && strchr(star_match[0], ':')) {
3300                       /* Avoid "tells you:" spoofs in channels */
3301                      tkind = 3;
3302                   }
3303                   if (star_match[0][0] == NULLCHAR ||
3304                       strchr(star_match[0], ' ') ||
3305                       (tkind == 3 && strchr(star_match[1], ' '))) {
3306                     /* Reject bogus matches */
3307                     i = oldi;
3308                   } else {
3309                     if (appData.colorize) {
3310                       if (oldi > next_out) {
3311                         SendToPlayer(&buf[next_out], oldi - next_out);
3312                         next_out = oldi;
3313                       }
3314                       switch (tkind) {
3315                       case 1:
3316                         Colorize(ColorTell, FALSE);
3317                         curColor = ColorTell;
3318                         break;
3319                       case 2:
3320                         Colorize(ColorKibitz, FALSE);
3321                         curColor = ColorKibitz;
3322                         break;
3323                       case 3:
3324                         p = strrchr(star_match[1], '(');
3325                         if (p == NULL) {
3326                           p = star_match[1];
3327                         } else {
3328                           p++;
3329                         }
3330                         if (atoi(p) == 1) {
3331                           Colorize(ColorChannel1, FALSE);
3332                           curColor = ColorChannel1;
3333                         } else {
3334                           Colorize(ColorChannel, FALSE);
3335                           curColor = ColorChannel;
3336                         }
3337                         break;
3338                       case 5:
3339                         curColor = ColorNormal;
3340                         break;
3341                       }
3342                     }
3343                     if (started == STARTED_NONE && appData.autoComment &&
3344                         (gameMode == IcsObserving ||
3345                          gameMode == IcsPlayingWhite ||
3346                          gameMode == IcsPlayingBlack)) {
3347                       parse_pos = i - oldi;
3348                       memcpy(parse, &buf[oldi], parse_pos);
3349                       parse[parse_pos] = NULLCHAR;
3350                       started = STARTED_COMMENT;
3351                       savingComment = TRUE;
3352                     } else {
3353                       started = STARTED_CHATTER;
3354                       savingComment = FALSE;
3355                     }
3356                     loggedOn = TRUE;
3357                     continue;
3358                   }
3359                 }
3360
3361                 if (looking_at(buf, &i, "* s-shouts: ") ||
3362                     looking_at(buf, &i, "* c-shouts: ")) {
3363                     if (appData.colorize) {
3364                         if (oldi > next_out) {
3365                             SendToPlayer(&buf[next_out], oldi - next_out);
3366                             next_out = oldi;
3367                         }
3368                         Colorize(ColorSShout, FALSE);
3369                         curColor = ColorSShout;
3370                     }
3371                     loggedOn = TRUE;
3372                     started = STARTED_CHATTER;
3373                     continue;
3374                 }
3375
3376                 if (looking_at(buf, &i, "--->")) {
3377                     loggedOn = TRUE;
3378                     continue;
3379                 }
3380
3381                 if (looking_at(buf, &i, "* shouts: ") ||
3382                     looking_at(buf, &i, "--> ")) {
3383                     if (appData.colorize) {
3384                         if (oldi > next_out) {
3385                             SendToPlayer(&buf[next_out], oldi - next_out);
3386                             next_out = oldi;
3387                         }
3388                         Colorize(ColorShout, FALSE);
3389                         curColor = ColorShout;
3390                     }
3391                     loggedOn = TRUE;
3392                     started = STARTED_CHATTER;
3393                     continue;
3394                 }
3395
3396                 if (looking_at( buf, &i, "Challenge:")) {
3397                     if (appData.colorize) {
3398                         if (oldi > next_out) {
3399                             SendToPlayer(&buf[next_out], oldi - next_out);
3400                             next_out = oldi;
3401                         }
3402                         Colorize(ColorChallenge, FALSE);
3403                         curColor = ColorChallenge;
3404                     }
3405                     loggedOn = TRUE;
3406                     continue;
3407                 }
3408
3409                 if (looking_at(buf, &i, "* offers you") ||
3410                     looking_at(buf, &i, "* offers to be") ||
3411                     looking_at(buf, &i, "* would like to") ||
3412                     looking_at(buf, &i, "* requests to") ||
3413                     looking_at(buf, &i, "Your opponent offers") ||
3414                     looking_at(buf, &i, "Your opponent requests")) {
3415
3416                     if (appData.colorize) {
3417                         if (oldi > next_out) {
3418                             SendToPlayer(&buf[next_out], oldi - next_out);
3419                             next_out = oldi;
3420                         }
3421                         Colorize(ColorRequest, FALSE);
3422                         curColor = ColorRequest;
3423                     }
3424                     continue;
3425                 }
3426
3427                 if (looking_at(buf, &i, "* (*) seeking")) {
3428                     if (appData.colorize) {
3429                         if (oldi > next_out) {
3430                             SendToPlayer(&buf[next_out], oldi - next_out);
3431                             next_out = oldi;
3432                         }
3433                         Colorize(ColorSeek, FALSE);
3434                         curColor = ColorSeek;
3435                     }
3436                     continue;
3437             }
3438
3439           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3440
3441             if (looking_at(buf, &i, "\\   ")) {
3442                 if (prevColor != ColorNormal) {
3443                     if (oldi > next_out) {
3444                         SendToPlayer(&buf[next_out], oldi - next_out);
3445                         next_out = oldi;
3446                     }
3447                     Colorize(prevColor, TRUE);
3448                     curColor = prevColor;
3449                 }
3450                 if (savingComment) {
3451                     parse_pos = i - oldi;
3452                     memcpy(parse, &buf[oldi], parse_pos);
3453                     parse[parse_pos] = NULLCHAR;
3454                     started = STARTED_COMMENT;
3455                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3456                         chattingPartner = savingComment - 3; // kludge to remember the box
3457                 } else {
3458                     started = STARTED_CHATTER;
3459                 }
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "Black Strength :") ||
3464                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3465                 looking_at(buf, &i, "<10>") ||
3466                 looking_at(buf, &i, "#@#")) {
3467                 /* Wrong board style */
3468                 loggedOn = TRUE;
3469                 SendToICS(ics_prefix);
3470                 SendToICS("set style 12\n");
3471                 SendToICS(ics_prefix);
3472                 SendToICS("refresh\n");
3473                 continue;
3474             }
3475
3476             if (looking_at(buf, &i, "login:")) {
3477               if (!have_sent_ICS_logon) {
3478                 if(ICSInitScript())
3479                   have_sent_ICS_logon = 1;
3480                 else // no init script was found
3481                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3482               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3483                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3484               }
3485                 continue;
3486             }
3487
3488             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3489                 (looking_at(buf, &i, "\n<12> ") ||
3490                  looking_at(buf, &i, "<12> "))) {
3491                 loggedOn = TRUE;
3492                 if (oldi > next_out) {
3493                     SendToPlayer(&buf[next_out], oldi - next_out);
3494                 }
3495                 next_out = i;
3496                 started = STARTED_BOARD;
3497                 parse_pos = 0;
3498                 continue;
3499             }
3500
3501             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3502                 looking_at(buf, &i, "<b1> ")) {
3503                 if (oldi > next_out) {
3504                     SendToPlayer(&buf[next_out], oldi - next_out);
3505                 }
3506                 next_out = i;
3507                 started = STARTED_HOLDINGS;
3508                 parse_pos = 0;
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3513                 loggedOn = TRUE;
3514                 /* Header for a move list -- first line */
3515
3516                 switch (ics_getting_history) {
3517                   case H_FALSE:
3518                     switch (gameMode) {
3519                       case IcsIdle:
3520                       case BeginningOfGame:
3521                         /* User typed "moves" or "oldmoves" while we
3522                            were idle.  Pretend we asked for these
3523                            moves and soak them up so user can step
3524                            through them and/or save them.
3525                            */
3526                         Reset(FALSE, TRUE);
3527                         gameMode = IcsObserving;
3528                         ModeHighlight();
3529                         ics_gamenum = -1;
3530                         ics_getting_history = H_GOT_UNREQ_HEADER;
3531                         break;
3532                       case EditGame: /*?*/
3533                       case EditPosition: /*?*/
3534                         /* Should above feature work in these modes too? */
3535                         /* For now it doesn't */
3536                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3537                         break;
3538                       default:
3539                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3540                         break;
3541                     }
3542                     break;
3543                   case H_REQUESTED:
3544                     /* Is this the right one? */
3545                     if (gameInfo.white && gameInfo.black &&
3546                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3547                         strcmp(gameInfo.black, star_match[2]) == 0) {
3548                         /* All is well */
3549                         ics_getting_history = H_GOT_REQ_HEADER;
3550                     }
3551                     break;
3552                   case H_GOT_REQ_HEADER:
3553                   case H_GOT_UNREQ_HEADER:
3554                   case H_GOT_UNWANTED_HEADER:
3555                   case H_GETTING_MOVES:
3556                     /* Should not happen */
3557                     DisplayError(_("Error gathering move list: two headers"), 0);
3558                     ics_getting_history = H_FALSE;
3559                     break;
3560                 }
3561
3562                 /* Save player ratings into gameInfo if needed */
3563                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3564                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3565                     (gameInfo.whiteRating == -1 ||
3566                      gameInfo.blackRating == -1)) {
3567
3568                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3569                     gameInfo.blackRating = string_to_rating(star_match[3]);
3570                     if (appData.debugMode)
3571                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3572                               gameInfo.whiteRating, gameInfo.blackRating);
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i,
3578               "* * match, initial time: * minute*, increment: * second")) {
3579                 /* Header for a move list -- second line */
3580                 /* Initial board will follow if this is a wild game */
3581                 if (gameInfo.event != NULL) free(gameInfo.event);
3582                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3583                 gameInfo.event = StrSave(str);
3584                 /* [HGM] we switched variant. Translate boards if needed. */
3585                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "Move  ")) {
3590                 /* Beginning of a move list */
3591                 switch (ics_getting_history) {
3592                   case H_FALSE:
3593                     /* Normally should not happen */
3594                     /* Maybe user hit reset while we were parsing */
3595                     break;
3596                   case H_REQUESTED:
3597                     /* Happens if we are ignoring a move list that is not
3598                      * the one we just requested.  Common if the user
3599                      * tries to observe two games without turning off
3600                      * getMoveList */
3601                     break;
3602                   case H_GETTING_MOVES:
3603                     /* Should not happen */
3604                     DisplayError(_("Error gathering move list: nested"), 0);
3605                     ics_getting_history = H_FALSE;
3606                     break;
3607                   case H_GOT_REQ_HEADER:
3608                     ics_getting_history = H_GETTING_MOVES;
3609                     started = STARTED_MOVES;
3610                     parse_pos = 0;
3611                     if (oldi > next_out) {
3612                         SendToPlayer(&buf[next_out], oldi - next_out);
3613                     }
3614                     break;
3615                   case H_GOT_UNREQ_HEADER:
3616                     ics_getting_history = H_GETTING_MOVES;
3617                     started = STARTED_MOVES_NOHIDE;
3618                     parse_pos = 0;
3619                     break;
3620                   case H_GOT_UNWANTED_HEADER:
3621                     ics_getting_history = H_FALSE;
3622                     break;
3623                 }
3624                 continue;
3625             }
3626
3627             if (looking_at(buf, &i, "% ") ||
3628                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3629                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3630                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3631                     soughtPending = FALSE;
3632                     seekGraphUp = TRUE;
3633                     DrawSeekGraph();
3634                 }
3635                 if(suppressKibitz) next_out = i;
3636                 savingComment = FALSE;
3637                 suppressKibitz = 0;
3638                 switch (started) {
3639                   case STARTED_MOVES:
3640                   case STARTED_MOVES_NOHIDE:
3641                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3642                     parse[parse_pos + i - oldi] = NULLCHAR;
3643                     ParseGameHistory(parse);
3644 #if ZIPPY
3645                     if (appData.zippyPlay && first.initDone) {
3646                         FeedMovesToProgram(&first, forwardMostMove);
3647                         if (gameMode == IcsPlayingWhite) {
3648                             if (WhiteOnMove(forwardMostMove)) {
3649                                 if (first.sendTime) {
3650                                   if (first.useColors) {
3651                                     SendToProgram("black\n", &first);
3652                                   }
3653                                   SendTimeRemaining(&first, TRUE);
3654                                 }
3655                                 if (first.useColors) {
3656                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3657                                 }
3658                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3659                                 first.maybeThinking = TRUE;
3660                             } else {
3661                                 if (first.usePlayother) {
3662                                   if (first.sendTime) {
3663                                     SendTimeRemaining(&first, TRUE);
3664                                   }
3665                                   SendToProgram("playother\n", &first);
3666                                   firstMove = FALSE;
3667                                 } else {
3668                                   firstMove = TRUE;
3669                                 }
3670                             }
3671                         } else if (gameMode == IcsPlayingBlack) {
3672                             if (!WhiteOnMove(forwardMostMove)) {
3673                                 if (first.sendTime) {
3674                                   if (first.useColors) {
3675                                     SendToProgram("white\n", &first);
3676                                   }
3677                                   SendTimeRemaining(&first, FALSE);
3678                                 }
3679                                 if (first.useColors) {
3680                                   SendToProgram("black\n", &first);
3681                                 }
3682                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3683                                 first.maybeThinking = TRUE;
3684                             } else {
3685                                 if (first.usePlayother) {
3686                                   if (first.sendTime) {
3687                                     SendTimeRemaining(&first, FALSE);
3688                                   }
3689                                   SendToProgram("playother\n", &first);
3690                                   firstMove = FALSE;
3691                                 } else {
3692                                   firstMove = TRUE;
3693                                 }
3694                             }
3695                         }
3696                     }
3697 #endif
3698                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3699                         /* Moves came from oldmoves or moves command
3700                            while we weren't doing anything else.
3701                            */
3702                         currentMove = forwardMostMove;
3703                         ClearHighlights();/*!!could figure this out*/
3704                         flipView = appData.flipView;
3705                         DrawPosition(TRUE, boards[currentMove]);
3706                         DisplayBothClocks();
3707                         snprintf(str, MSG_SIZ, "%s %s %s",
3708                                 gameInfo.white, _("vs."),  gameInfo.black);
3709                         DisplayTitle(str);
3710                         gameMode = IcsIdle;
3711                     } else {
3712                         /* Moves were history of an active game */
3713                         if (gameInfo.resultDetails != NULL) {
3714                             free(gameInfo.resultDetails);
3715                             gameInfo.resultDetails = NULL;
3716                         }
3717                     }
3718                     HistorySet(parseList, backwardMostMove,
3719                                forwardMostMove, currentMove-1);
3720                     DisplayMove(currentMove - 1);
3721                     if (started == STARTED_MOVES) next_out = i;
3722                     started = STARTED_NONE;
3723                     ics_getting_history = H_FALSE;
3724                     break;
3725
3726                   case STARTED_OBSERVE:
3727                     started = STARTED_NONE;
3728                     SendToICS(ics_prefix);
3729                     SendToICS("refresh\n");
3730                     break;
3731
3732                   default:
3733                     break;
3734                 }
3735                 if(bookHit) { // [HGM] book: simulate book reply
3736                     static char bookMove[MSG_SIZ]; // a bit generous?
3737
3738                     programStats.nodes = programStats.depth = programStats.time =
3739                     programStats.score = programStats.got_only_move = 0;
3740                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3741
3742                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3743                     strcat(bookMove, bookHit);
3744                     HandleMachineMove(bookMove, &first);
3745                 }
3746                 continue;
3747             }
3748
3749             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3750                  started == STARTED_HOLDINGS ||
3751                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3752                 /* Accumulate characters in move list or board */
3753                 parse[parse_pos++] = buf[i];
3754             }
3755
3756             /* Start of game messages.  Mostly we detect start of game
3757                when the first board image arrives.  On some versions
3758                of the ICS, though, we need to do a "refresh" after starting
3759                to observe in order to get the current board right away. */
3760             if (looking_at(buf, &i, "Adding game * to observation list")) {
3761                 started = STARTED_OBSERVE;
3762                 continue;
3763             }
3764
3765             /* Handle auto-observe */
3766             if (appData.autoObserve &&
3767                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3768                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3769                 char *player;
3770                 /* Choose the player that was highlighted, if any. */
3771                 if (star_match[0][0] == '\033' ||
3772                     star_match[1][0] != '\033') {
3773                     player = star_match[0];
3774                 } else {
3775                     player = star_match[2];
3776                 }
3777                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3778                         ics_prefix, StripHighlightAndTitle(player));
3779                 SendToICS(str);
3780
3781                 /* Save ratings from notify string */
3782                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3783                 player1Rating = string_to_rating(star_match[1]);
3784                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3785                 player2Rating = string_to_rating(star_match[3]);
3786
3787                 if (appData.debugMode)
3788                   fprintf(debugFP,
3789                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3790                           player1Name, player1Rating,
3791                           player2Name, player2Rating);
3792
3793                 continue;
3794             }
3795
3796             /* Deal with automatic examine mode after a game,
3797                and with IcsObserving -> IcsExamining transition */
3798             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3799                 looking_at(buf, &i, "has made you an examiner of game *")) {
3800
3801                 int gamenum = atoi(star_match[0]);
3802                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3803                     gamenum == ics_gamenum) {
3804                     /* We were already playing or observing this game;
3805                        no need to refetch history */
3806                     gameMode = IcsExamining;
3807                     if (pausing) {
3808                         pauseExamForwardMostMove = forwardMostMove;
3809                     } else if (currentMove < forwardMostMove) {
3810                         ForwardInner(forwardMostMove);
3811                     }
3812                 } else {
3813                     /* I don't think this case really can happen */
3814                     SendToICS(ics_prefix);
3815                     SendToICS("refresh\n");
3816                 }
3817                 continue;
3818             }
3819
3820             /* Error messages */
3821 //          if (ics_user_moved) {
3822             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3823                 if (looking_at(buf, &i, "Illegal move") ||
3824                     looking_at(buf, &i, "Not a legal move") ||
3825                     looking_at(buf, &i, "Your king is in check") ||
3826                     looking_at(buf, &i, "It isn't your turn") ||
3827                     looking_at(buf, &i, "It is not your move")) {
3828                     /* Illegal move */
3829                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3830                         currentMove = forwardMostMove-1;
3831                         DisplayMove(currentMove - 1); /* before DMError */
3832                         DrawPosition(FALSE, boards[currentMove]);
3833                         SwitchClocks(forwardMostMove-1); // [HGM] race
3834                         DisplayBothClocks();
3835                     }
3836                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3837                     ics_user_moved = 0;
3838                     continue;
3839                 }
3840             }
3841
3842             if (looking_at(buf, &i, "still have time") ||
3843                 looking_at(buf, &i, "not out of time") ||
3844                 looking_at(buf, &i, "either player is out of time") ||
3845                 looking_at(buf, &i, "has timeseal; checking")) {
3846                 /* We must have called his flag a little too soon */
3847                 whiteFlag = blackFlag = FALSE;
3848                 continue;
3849             }
3850
3851             if (looking_at(buf, &i, "added * seconds to") ||
3852                 looking_at(buf, &i, "seconds were added to")) {
3853                 /* Update the clocks */
3854                 SendToICS(ics_prefix);
3855                 SendToICS("refresh\n");
3856                 continue;
3857             }
3858
3859             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3860                 ics_clock_paused = TRUE;
3861                 StopClocks();
3862                 continue;
3863             }
3864
3865             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3866                 ics_clock_paused = FALSE;
3867                 StartClocks();
3868                 continue;
3869             }
3870
3871             /* Grab player ratings from the Creating: message.
3872                Note we have to check for the special case when
3873                the ICS inserts things like [white] or [black]. */
3874             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3875                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3876                 /* star_matches:
3877                    0    player 1 name (not necessarily white)
3878                    1    player 1 rating
3879                    2    empty, white, or black (IGNORED)
3880                    3    player 2 name (not necessarily black)
3881                    4    player 2 rating
3882
3883                    The names/ratings are sorted out when the game
3884                    actually starts (below).
3885                 */
3886                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3887                 player1Rating = string_to_rating(star_match[1]);
3888                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3889                 player2Rating = string_to_rating(star_match[4]);
3890
3891                 if (appData.debugMode)
3892                   fprintf(debugFP,
3893                           "Ratings from 'Creating:' %s %d, %s %d\n",
3894                           player1Name, player1Rating,
3895                           player2Name, player2Rating);
3896
3897                 continue;
3898             }
3899
3900             /* Improved generic start/end-of-game messages */
3901             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3902                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3903                 /* If tkind == 0: */
3904                 /* star_match[0] is the game number */
3905                 /*           [1] is the white player's name */
3906                 /*           [2] is the black player's name */
3907                 /* For end-of-game: */
3908                 /*           [3] is the reason for the game end */
3909                 /*           [4] is a PGN end game-token, preceded by " " */
3910                 /* For start-of-game: */
3911                 /*           [3] begins with "Creating" or "Continuing" */
3912                 /*           [4] is " *" or empty (don't care). */
3913                 int gamenum = atoi(star_match[0]);
3914                 char *whitename, *blackname, *why, *endtoken;
3915                 ChessMove endtype = EndOfFile;
3916
3917                 if (tkind == 0) {
3918                   whitename = star_match[1];
3919                   blackname = star_match[2];
3920                   why = star_match[3];
3921                   endtoken = star_match[4];
3922                 } else {
3923                   whitename = star_match[1];
3924                   blackname = star_match[3];
3925                   why = star_match[5];
3926                   endtoken = star_match[6];
3927                 }
3928
3929                 /* Game start messages */
3930                 if (strncmp(why, "Creating ", 9) == 0 ||
3931                     strncmp(why, "Continuing ", 11) == 0) {
3932                     gs_gamenum = gamenum;
3933                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3934                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3935                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3936 #if ZIPPY
3937                     if (appData.zippyPlay) {
3938                         ZippyGameStart(whitename, blackname);
3939                     }
3940 #endif /*ZIPPY*/
3941                     partnerBoardValid = FALSE; // [HGM] bughouse
3942                     continue;
3943                 }
3944
3945                 /* Game end messages */
3946                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3947                     ics_gamenum != gamenum) {
3948                     continue;
3949                 }
3950                 while (endtoken[0] == ' ') endtoken++;
3951                 switch (endtoken[0]) {
3952                   case '*':
3953                   default:
3954                     endtype = GameUnfinished;
3955                     break;
3956                   case '0':
3957                     endtype = BlackWins;
3958                     break;
3959                   case '1':
3960                     if (endtoken[1] == '/')
3961                       endtype = GameIsDrawn;
3962                     else
3963                       endtype = WhiteWins;
3964                     break;
3965                 }
3966                 GameEnds(endtype, why, GE_ICS);
3967 #if ZIPPY
3968                 if (appData.zippyPlay && first.initDone) {
3969                     ZippyGameEnd(endtype, why);
3970                     if (first.pr == NoProc) {
3971                       /* Start the next process early so that we'll
3972                          be ready for the next challenge */
3973                       StartChessProgram(&first);
3974                     }
3975                     /* Send "new" early, in case this command takes
3976                        a long time to finish, so that we'll be ready
3977                        for the next challenge. */
3978                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3979                     Reset(TRUE, TRUE);
3980                 }
3981 #endif /*ZIPPY*/
3982                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3983                 continue;
3984             }
3985
3986             if (looking_at(buf, &i, "Removing game * from observation") ||
3987                 looking_at(buf, &i, "no longer observing game *") ||
3988                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3989                 if (gameMode == IcsObserving &&
3990                     atoi(star_match[0]) == ics_gamenum)
3991                   {
3992                       /* icsEngineAnalyze */
3993                       if (appData.icsEngineAnalyze) {
3994                             ExitAnalyzeMode();
3995                             ModeHighlight();
3996                       }
3997                       StopClocks();
3998                       gameMode = IcsIdle;
3999                       ics_gamenum = -1;
4000                       ics_user_moved = FALSE;
4001                   }
4002                 continue;
4003             }
4004
4005             if (looking_at(buf, &i, "no longer examining game *")) {
4006                 if (gameMode == IcsExamining &&
4007                     atoi(star_match[0]) == ics_gamenum)
4008                   {
4009                       gameMode = IcsIdle;
4010                       ics_gamenum = -1;
4011                       ics_user_moved = FALSE;
4012                   }
4013                 continue;
4014             }
4015
4016             /* Advance leftover_start past any newlines we find,
4017                so only partial lines can get reparsed */
4018             if (looking_at(buf, &i, "\n")) {
4019                 prevColor = curColor;
4020                 if (curColor != ColorNormal) {
4021                     if (oldi > next_out) {
4022                         SendToPlayer(&buf[next_out], oldi - next_out);
4023                         next_out = oldi;
4024                     }
4025                     Colorize(ColorNormal, FALSE);
4026                     curColor = ColorNormal;
4027                 }
4028                 if (started == STARTED_BOARD) {
4029                     started = STARTED_NONE;
4030                     parse[parse_pos] = NULLCHAR;
4031                     ParseBoard12(parse);
4032                     ics_user_moved = 0;
4033
4034                     /* Send premove here */
4035                     if (appData.premove) {
4036                       char str[MSG_SIZ];
4037                       if (currentMove == 0 &&
4038                           gameMode == IcsPlayingWhite &&
4039                           appData.premoveWhite) {
4040                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4041                         if (appData.debugMode)
4042                           fprintf(debugFP, "Sending premove:\n");
4043                         SendToICS(str);
4044                       } else if (currentMove == 1 &&
4045                                  gameMode == IcsPlayingBlack &&
4046                                  appData.premoveBlack) {
4047                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4048                         if (appData.debugMode)
4049                           fprintf(debugFP, "Sending premove:\n");
4050                         SendToICS(str);
4051                       } else if (gotPremove) {
4052                         gotPremove = 0;
4053                         ClearPremoveHighlights();
4054                         if (appData.debugMode)
4055                           fprintf(debugFP, "Sending premove:\n");
4056                           UserMoveEvent(premoveFromX, premoveFromY,
4057                                         premoveToX, premoveToY,
4058                                         premovePromoChar);
4059                       }
4060                     }
4061
4062                     /* Usually suppress following prompt */
4063                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4064                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4065                         if (looking_at(buf, &i, "*% ")) {
4066                             savingComment = FALSE;
4067                             suppressKibitz = 0;
4068                         }
4069                     }
4070                     next_out = i;
4071                 } else if (started == STARTED_HOLDINGS) {
4072                     int gamenum;
4073                     char new_piece[MSG_SIZ];
4074                     started = STARTED_NONE;
4075                     parse[parse_pos] = NULLCHAR;
4076                     if (appData.debugMode)
4077                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4078                                                         parse, currentMove);
4079                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4080                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4081                         if (gameInfo.variant == VariantNormal) {
4082                           /* [HGM] We seem to switch variant during a game!
4083                            * Presumably no holdings were displayed, so we have
4084                            * to move the position two files to the right to
4085                            * create room for them!
4086                            */
4087                           VariantClass newVariant;
4088                           switch(gameInfo.boardWidth) { // base guess on board width
4089                                 case 9:  newVariant = VariantShogi; break;
4090                                 case 10: newVariant = VariantGreat; break;
4091                                 default: newVariant = VariantCrazyhouse; break;
4092                           }
4093                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4094                           /* Get a move list just to see the header, which
4095                              will tell us whether this is really bug or zh */
4096                           if (ics_getting_history == H_FALSE) {
4097                             ics_getting_history = H_REQUESTED;
4098                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4099                             SendToICS(str);
4100                           }
4101                         }
4102                         new_piece[0] = NULLCHAR;
4103                         sscanf(parse, "game %d white [%s black [%s <- %s",
4104                                &gamenum, white_holding, black_holding,
4105                                new_piece);
4106                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4107                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4108                         /* [HGM] copy holdings to board holdings area */
4109                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4110                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4111                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4112 #if ZIPPY
4113                         if (appData.zippyPlay && first.initDone) {
4114                             ZippyHoldings(white_holding, black_holding,
4115                                           new_piece);
4116                         }
4117 #endif /*ZIPPY*/
4118                         if (tinyLayout || smallLayout) {
4119                             char wh[16], bh[16];
4120                             PackHolding(wh, white_holding);
4121                             PackHolding(bh, black_holding);
4122                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4123                                     gameInfo.white, gameInfo.black);
4124                         } else {
4125                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4126                                     gameInfo.white, white_holding, _("vs."),
4127                                     gameInfo.black, black_holding);
4128                         }
4129                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4130                         DrawPosition(FALSE, boards[currentMove]);
4131                         DisplayTitle(str);
4132                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4133                         sscanf(parse, "game %d white [%s black [%s <- %s",
4134                                &gamenum, white_holding, black_holding,
4135                                new_piece);
4136                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4137                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4138                         /* [HGM] copy holdings to partner-board holdings area */
4139                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4140                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4141                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4142                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4143                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4144                       }
4145                     }
4146                     /* Suppress following prompt */
4147                     if (looking_at(buf, &i, "*% ")) {
4148                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4149                         savingComment = FALSE;
4150                         suppressKibitz = 0;
4151                     }
4152                     next_out = i;
4153                 }
4154                 continue;
4155             }
4156
4157             i++;                /* skip unparsed character and loop back */
4158         }
4159
4160         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4161 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4162 //          SendToPlayer(&buf[next_out], i - next_out);
4163             started != STARTED_HOLDINGS && leftover_start > next_out) {
4164             SendToPlayer(&buf[next_out], leftover_start - next_out);
4165             next_out = i;
4166         }
4167
4168         leftover_len = buf_len - leftover_start;
4169         /* if buffer ends with something we couldn't parse,
4170            reparse it after appending the next read */
4171
4172     } else if (count == 0) {
4173         RemoveInputSource(isr);
4174         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4175     } else {
4176         DisplayFatalError(_("Error reading from ICS"), error, 1);
4177     }
4178 }
4179
4180
4181 /* Board style 12 looks like this:
4182
4183    <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
4184
4185  * The "<12> " is stripped before it gets to this routine.  The two
4186  * trailing 0's (flip state and clock ticking) are later addition, and
4187  * some chess servers may not have them, or may have only the first.
4188  * Additional trailing fields may be added in the future.
4189  */
4190
4191 #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"
4192
4193 #define RELATION_OBSERVING_PLAYED    0
4194 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4195 #define RELATION_PLAYING_MYMOVE      1
4196 #define RELATION_PLAYING_NOTMYMOVE  -1
4197 #define RELATION_EXAMINING           2
4198 #define RELATION_ISOLATED_BOARD     -3
4199 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4200
4201 void
4202 ParseBoard12 (char *string)
4203 {
4204 #if ZIPPY
4205     int i, takeback;
4206     char *bookHit = NULL; // [HGM] book
4207 #endif
4208     GameMode newGameMode;
4209     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4210     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4211     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4212     char to_play, board_chars[200];
4213     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4214     char black[32], white[32];
4215     Board board;
4216     int prevMove = currentMove;
4217     int ticking = 2;
4218     ChessMove moveType;
4219     int fromX, fromY, toX, toY;
4220     char promoChar;
4221     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4222     Boolean weird = FALSE, reqFlag = FALSE;
4223
4224     fromX = fromY = toX = toY = -1;
4225
4226     newGame = FALSE;
4227
4228     if (appData.debugMode)
4229       fprintf(debugFP, "Parsing board: %s\n", string);
4230
4231     move_str[0] = NULLCHAR;
4232     elapsed_time[0] = NULLCHAR;
4233     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4234         int  i = 0, j;
4235         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4236             if(string[i] == ' ') { ranks++; files = 0; }
4237             else files++;
4238             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4239             i++;
4240         }
4241         for(j = 0; j <i; j++) board_chars[j] = string[j];
4242         board_chars[i] = '\0';
4243         string += i + 1;
4244     }
4245     n = sscanf(string, PATTERN, &to_play, &double_push,
4246                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4247                &gamenum, white, black, &relation, &basetime, &increment,
4248                &white_stren, &black_stren, &white_time, &black_time,
4249                &moveNum, str, elapsed_time, move_str, &ics_flip,
4250                &ticking);
4251
4252     if (n < 21) {
4253         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4254         DisplayError(str, 0);
4255         return;
4256     }
4257
4258     /* Convert the move number to internal form */
4259     moveNum = (moveNum - 1) * 2;
4260     if (to_play == 'B') moveNum++;
4261     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4262       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4263                         0, 1);
4264       return;
4265     }
4266
4267     switch (relation) {
4268       case RELATION_OBSERVING_PLAYED:
4269       case RELATION_OBSERVING_STATIC:
4270         if (gamenum == -1) {
4271             /* Old ICC buglet */
4272             relation = RELATION_OBSERVING_STATIC;
4273         }
4274         newGameMode = IcsObserving;
4275         break;
4276       case RELATION_PLAYING_MYMOVE:
4277       case RELATION_PLAYING_NOTMYMOVE:
4278         newGameMode =
4279           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4280             IcsPlayingWhite : IcsPlayingBlack;
4281         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4282         break;
4283       case RELATION_EXAMINING:
4284         newGameMode = IcsExamining;
4285         break;
4286       case RELATION_ISOLATED_BOARD:
4287       default:
4288         /* Just display this board.  If user was doing something else,
4289            we will forget about it until the next board comes. */
4290         newGameMode = IcsIdle;
4291         break;
4292       case RELATION_STARTING_POSITION:
4293         newGameMode = gameMode;
4294         break;
4295     }
4296
4297     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4298         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4299          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4300       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4301       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4302       static int lastBgGame = -1;
4303       char *toSqr;
4304       for (k = 0; k < ranks; k++) {
4305         for (j = 0; j < files; j++)
4306           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4307         if(gameInfo.holdingsWidth > 1) {
4308              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4309              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4310         }
4311       }
4312       CopyBoard(partnerBoard, board);
4313       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4314         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4315         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4316       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4317       if(toSqr = strchr(str, '-')) {
4318         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4319         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4320       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4321       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4322       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4323       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4324       if(twoBoards) {
4325           DisplayWhiteClock(white_time*fac, to_play == 'W');
4326           DisplayBlackClock(black_time*fac, to_play != 'W');
4327           activePartner = to_play;
4328           if(gamenum != lastBgGame) {
4329               char buf[MSG_SIZ];
4330               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4331               DisplayTitle(buf);
4332           }
4333           lastBgGame = gamenum;
4334           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4335                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4336       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4337                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4338       if(!twoBoards) DisplayMessage(partnerStatus, "");
4339         partnerBoardValid = TRUE;
4340       return;
4341     }
4342
4343     if(appData.dualBoard && appData.bgObserve) {
4344         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4345             SendToICS(ics_prefix), SendToICS("pobserve\n");
4346         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4347             char buf[MSG_SIZ];
4348             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4349             SendToICS(buf);
4350         }
4351     }
4352
4353     /* Modify behavior for initial board display on move listing
4354        of wild games.
4355        */
4356     switch (ics_getting_history) {
4357       case H_FALSE:
4358       case H_REQUESTED:
4359         break;
4360       case H_GOT_REQ_HEADER:
4361       case H_GOT_UNREQ_HEADER:
4362         /* This is the initial position of the current game */
4363         gamenum = ics_gamenum;
4364         moveNum = 0;            /* old ICS bug workaround */
4365         if (to_play == 'B') {
4366           startedFromSetupPosition = TRUE;
4367           blackPlaysFirst = TRUE;
4368           moveNum = 1;
4369           if (forwardMostMove == 0) forwardMostMove = 1;
4370           if (backwardMostMove == 0) backwardMostMove = 1;
4371           if (currentMove == 0) currentMove = 1;
4372         }
4373         newGameMode = gameMode;
4374         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4375         break;
4376       case H_GOT_UNWANTED_HEADER:
4377         /* This is an initial board that we don't want */
4378         return;
4379       case H_GETTING_MOVES:
4380         /* Should not happen */
4381         DisplayError(_("Error gathering move list: extra board"), 0);
4382         ics_getting_history = H_FALSE;
4383         return;
4384     }
4385
4386    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4387                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4388                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4389      /* [HGM] We seem to have switched variant unexpectedly
4390       * Try to guess new variant from board size
4391       */
4392           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4393           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4394           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4395           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4396           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4397           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4398           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4399           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4400           /* Get a move list just to see the header, which
4401              will tell us whether this is really bug or zh */
4402           if (ics_getting_history == H_FALSE) {
4403             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4404             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4405             SendToICS(str);
4406           }
4407     }
4408
4409     /* Take action if this is the first board of a new game, or of a
4410        different game than is currently being displayed.  */
4411     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4412         relation == RELATION_ISOLATED_BOARD) {
4413
4414         /* Forget the old game and get the history (if any) of the new one */
4415         if (gameMode != BeginningOfGame) {
4416           Reset(TRUE, TRUE);
4417         }
4418         newGame = TRUE;
4419         if (appData.autoRaiseBoard) BoardToTop();
4420         prevMove = -3;
4421         if (gamenum == -1) {
4422             newGameMode = IcsIdle;
4423         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4424                    appData.getMoveList && !reqFlag) {
4425             /* Need to get game history */
4426             ics_getting_history = H_REQUESTED;
4427             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4428             SendToICS(str);
4429         }
4430
4431         /* Initially flip the board to have black on the bottom if playing
4432            black or if the ICS flip flag is set, but let the user change
4433            it with the Flip View button. */
4434         flipView = appData.autoFlipView ?
4435           (newGameMode == IcsPlayingBlack) || ics_flip :
4436           appData.flipView;
4437
4438         /* Done with values from previous mode; copy in new ones */
4439         gameMode = newGameMode;
4440         ModeHighlight();
4441         ics_gamenum = gamenum;
4442         if (gamenum == gs_gamenum) {
4443             int klen = strlen(gs_kind);
4444             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4445             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4446             gameInfo.event = StrSave(str);
4447         } else {
4448             gameInfo.event = StrSave("ICS game");
4449         }
4450         gameInfo.site = StrSave(appData.icsHost);
4451         gameInfo.date = PGNDate();
4452         gameInfo.round = StrSave("-");
4453         gameInfo.white = StrSave(white);
4454         gameInfo.black = StrSave(black);
4455         timeControl = basetime * 60 * 1000;
4456         timeControl_2 = 0;
4457         timeIncrement = increment * 1000;
4458         movesPerSession = 0;
4459         gameInfo.timeControl = TimeControlTagValue();
4460         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4461   if (appData.debugMode) {
4462     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4463     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4464     setbuf(debugFP, NULL);
4465   }
4466
4467         gameInfo.outOfBook = NULL;
4468
4469         /* Do we have the ratings? */
4470         if (strcmp(player1Name, white) == 0 &&
4471             strcmp(player2Name, black) == 0) {
4472             if (appData.debugMode)
4473               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4474                       player1Rating, player2Rating);
4475             gameInfo.whiteRating = player1Rating;
4476             gameInfo.blackRating = player2Rating;
4477         } else if (strcmp(player2Name, white) == 0 &&
4478                    strcmp(player1Name, black) == 0) {
4479             if (appData.debugMode)
4480               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4481                       player2Rating, player1Rating);
4482             gameInfo.whiteRating = player2Rating;
4483             gameInfo.blackRating = player1Rating;
4484         }
4485         player1Name[0] = player2Name[0] = NULLCHAR;
4486
4487         /* Silence shouts if requested */
4488         if (appData.quietPlay &&
4489             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4490             SendToICS(ics_prefix);
4491             SendToICS("set shout 0\n");
4492         }
4493     }
4494
4495     /* Deal with midgame name changes */
4496     if (!newGame) {
4497         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4498             if (gameInfo.white) free(gameInfo.white);
4499             gameInfo.white = StrSave(white);
4500         }
4501         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4502             if (gameInfo.black) free(gameInfo.black);
4503             gameInfo.black = StrSave(black);
4504         }
4505     }
4506
4507     /* Throw away game result if anything actually changes in examine mode */
4508     if (gameMode == IcsExamining && !newGame) {
4509         gameInfo.result = GameUnfinished;
4510         if (gameInfo.resultDetails != NULL) {
4511             free(gameInfo.resultDetails);
4512             gameInfo.resultDetails = NULL;
4513         }
4514     }
4515
4516     /* In pausing && IcsExamining mode, we ignore boards coming
4517        in if they are in a different variation than we are. */
4518     if (pauseExamInvalid) return;
4519     if (pausing && gameMode == IcsExamining) {
4520         if (moveNum <= pauseExamForwardMostMove) {
4521             pauseExamInvalid = TRUE;
4522             forwardMostMove = pauseExamForwardMostMove;
4523             return;
4524         }
4525     }
4526
4527   if (appData.debugMode) {
4528     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4529   }
4530     /* Parse the board */
4531     for (k = 0; k < ranks; k++) {
4532       for (j = 0; j < files; j++)
4533         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4534       if(gameInfo.holdingsWidth > 1) {
4535            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4536            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4537       }
4538     }
4539     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4540       board[5][BOARD_RGHT+1] = WhiteAngel;
4541       board[6][BOARD_RGHT+1] = WhiteMarshall;
4542       board[1][0] = BlackMarshall;
4543       board[2][0] = BlackAngel;
4544       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4545     }
4546     CopyBoard(boards[moveNum], board);
4547     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4548     if (moveNum == 0) {
4549         startedFromSetupPosition =
4550           !CompareBoards(board, initialPosition);
4551         if(startedFromSetupPosition)
4552             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4553     }
4554
4555     /* [HGM] Set castling rights. Take the outermost Rooks,
4556        to make it also work for FRC opening positions. Note that board12
4557        is really defective for later FRC positions, as it has no way to
4558        indicate which Rook can castle if they are on the same side of King.
4559        For the initial position we grant rights to the outermost Rooks,
4560        and remember thos rights, and we then copy them on positions
4561        later in an FRC game. This means WB might not recognize castlings with
4562        Rooks that have moved back to their original position as illegal,
4563        but in ICS mode that is not its job anyway.
4564     */
4565     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4566     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4567
4568         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4569             if(board[0][i] == WhiteRook) j = i;
4570         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4571         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4572             if(board[0][i] == WhiteRook) j = i;
4573         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4574         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4575             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4576         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4577         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4578             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4579         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4580
4581         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4582         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4583         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4584             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4585         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4586             if(board[BOARD_HEIGHT-1][k] == bKing)
4587                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4588         if(gameInfo.variant == VariantTwoKings) {
4589             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4590             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4591             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4592         }
4593     } else { int r;
4594         r = boards[moveNum][CASTLING][0] = initialRights[0];
4595         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4596         r = boards[moveNum][CASTLING][1] = initialRights[1];
4597         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4598         r = boards[moveNum][CASTLING][3] = initialRights[3];
4599         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4600         r = boards[moveNum][CASTLING][4] = initialRights[4];
4601         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4602         /* wildcastle kludge: always assume King has rights */
4603         r = boards[moveNum][CASTLING][2] = initialRights[2];
4604         r = boards[moveNum][CASTLING][5] = initialRights[5];
4605     }
4606     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4607     boards[moveNum][EP_STATUS] = EP_NONE;
4608     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4609     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4610     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4611
4612
4613     if (ics_getting_history == H_GOT_REQ_HEADER ||
4614         ics_getting_history == H_GOT_UNREQ_HEADER) {
4615         /* This was an initial position from a move list, not
4616            the current position */
4617         return;
4618     }
4619
4620     /* Update currentMove and known move number limits */
4621     newMove = newGame || moveNum > forwardMostMove;
4622
4623     if (newGame) {
4624         forwardMostMove = backwardMostMove = currentMove = moveNum;
4625         if (gameMode == IcsExamining && moveNum == 0) {
4626           /* Workaround for ICS limitation: we are not told the wild
4627              type when starting to examine a game.  But if we ask for
4628              the move list, the move list header will tell us */
4629             ics_getting_history = H_REQUESTED;
4630             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4631             SendToICS(str);
4632         }
4633     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4634                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4635 #if ZIPPY
4636         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4637         /* [HGM] applied this also to an engine that is silently watching        */
4638         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4639             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4640             gameInfo.variant == currentlyInitializedVariant) {
4641           takeback = forwardMostMove - moveNum;
4642           for (i = 0; i < takeback; i++) {
4643             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4644             SendToProgram("undo\n", &first);
4645           }
4646         }
4647 #endif
4648
4649         forwardMostMove = moveNum;
4650         if (!pausing || currentMove > forwardMostMove)
4651           currentMove = forwardMostMove;
4652     } else {
4653         /* New part of history that is not contiguous with old part */
4654         if (pausing && gameMode == IcsExamining) {
4655             pauseExamInvalid = TRUE;
4656             forwardMostMove = pauseExamForwardMostMove;
4657             return;
4658         }
4659         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4660 #if ZIPPY
4661             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4662                 // [HGM] when we will receive the move list we now request, it will be
4663                 // fed to the engine from the first move on. So if the engine is not
4664                 // in the initial position now, bring it there.
4665                 InitChessProgram(&first, 0);
4666             }
4667 #endif
4668             ics_getting_history = H_REQUESTED;
4669             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4670             SendToICS(str);
4671         }
4672         forwardMostMove = backwardMostMove = currentMove = moveNum;
4673     }
4674
4675     /* Update the clocks */
4676     if (strchr(elapsed_time, '.')) {
4677       /* Time is in ms */
4678       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4679       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4680     } else {
4681       /* Time is in seconds */
4682       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4683       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4684     }
4685
4686
4687 #if ZIPPY
4688     if (appData.zippyPlay && newGame &&
4689         gameMode != IcsObserving && gameMode != IcsIdle &&
4690         gameMode != IcsExamining)
4691       ZippyFirstBoard(moveNum, basetime, increment);
4692 #endif
4693
4694     /* Put the move on the move list, first converting
4695        to canonical algebraic form. */
4696     if (moveNum > 0) {
4697   if (appData.debugMode) {
4698     int f = forwardMostMove;
4699     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4700             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4701             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4702     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4703     fprintf(debugFP, "moveNum = %d\n", moveNum);
4704     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4705     setbuf(debugFP, NULL);
4706   }
4707         if (moveNum <= backwardMostMove) {
4708             /* We don't know what the board looked like before
4709                this move.  Punt. */
4710           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4711             strcat(parseList[moveNum - 1], " ");
4712             strcat(parseList[moveNum - 1], elapsed_time);
4713             moveList[moveNum - 1][0] = NULLCHAR;
4714         } else if (strcmp(move_str, "none") == 0) {
4715             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4716             /* Again, we don't know what the board looked like;
4717                this is really the start of the game. */
4718             parseList[moveNum - 1][0] = NULLCHAR;
4719             moveList[moveNum - 1][0] = NULLCHAR;
4720             backwardMostMove = moveNum;
4721             startedFromSetupPosition = TRUE;
4722             fromX = fromY = toX = toY = -1;
4723         } else {
4724           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4725           //                 So we parse the long-algebraic move string in stead of the SAN move
4726           int valid; char buf[MSG_SIZ], *prom;
4727
4728           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4729                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4730           // str looks something like "Q/a1-a2"; kill the slash
4731           if(str[1] == '/')
4732             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4733           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4734           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4735                 strcat(buf, prom); // long move lacks promo specification!
4736           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4737                 if(appData.debugMode)
4738                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4739                 safeStrCpy(move_str, buf, MSG_SIZ);
4740           }
4741           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4742                                 &fromX, &fromY, &toX, &toY, &promoChar)
4743                || ParseOneMove(buf, moveNum - 1, &moveType,
4744                                 &fromX, &fromY, &toX, &toY, &promoChar);
4745           // end of long SAN patch
4746           if (valid) {
4747             (void) CoordsToAlgebraic(boards[moveNum - 1],
4748                                      PosFlags(moveNum - 1),
4749                                      fromY, fromX, toY, toX, promoChar,
4750                                      parseList[moveNum-1]);
4751             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4752               case MT_NONE:
4753               case MT_STALEMATE:
4754               default:
4755                 break;
4756               case MT_CHECK:
4757                 if(gameInfo.variant != VariantShogi)
4758                     strcat(parseList[moveNum - 1], "+");
4759                 break;
4760               case MT_CHECKMATE:
4761               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4762                 strcat(parseList[moveNum - 1], "#");
4763                 break;
4764             }
4765             strcat(parseList[moveNum - 1], " ");
4766             strcat(parseList[moveNum - 1], elapsed_time);
4767             /* currentMoveString is set as a side-effect of ParseOneMove */
4768             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4769             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4770             strcat(moveList[moveNum - 1], "\n");
4771
4772             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4773                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4774               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4775                 ChessSquare old, new = boards[moveNum][k][j];
4776                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4777                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4778                   if(old == new) continue;
4779                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4780                   else if(new == WhiteWazir || new == BlackWazir) {
4781                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4782                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4783                       else boards[moveNum][k][j] = old; // preserve type of Gold
4784                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4785                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4786               }
4787           } else {
4788             /* Move from ICS was illegal!?  Punt. */
4789             if (appData.debugMode) {
4790               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4791               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4792             }
4793             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4794             strcat(parseList[moveNum - 1], " ");
4795             strcat(parseList[moveNum - 1], elapsed_time);
4796             moveList[moveNum - 1][0] = NULLCHAR;
4797             fromX = fromY = toX = toY = -1;
4798           }
4799         }
4800   if (appData.debugMode) {
4801     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4802     setbuf(debugFP, NULL);
4803   }
4804
4805 #if ZIPPY
4806         /* Send move to chess program (BEFORE animating it). */
4807         if (appData.zippyPlay && !newGame && newMove &&
4808            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4809
4810             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4811                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4812                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4813                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4814                             move_str);
4815                     DisplayError(str, 0);
4816                 } else {
4817                     if (first.sendTime) {
4818                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4819                     }
4820                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4821                     if (firstMove && !bookHit) {
4822                         firstMove = FALSE;
4823                         if (first.useColors) {
4824                           SendToProgram(gameMode == IcsPlayingWhite ?
4825                                         "white\ngo\n" :
4826                                         "black\ngo\n", &first);
4827                         } else {
4828                           SendToProgram("go\n", &first);
4829                         }
4830                         first.maybeThinking = TRUE;
4831                     }
4832                 }
4833             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4834               if (moveList[moveNum - 1][0] == NULLCHAR) {
4835                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4836                 DisplayError(str, 0);
4837               } else {
4838                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4839                 SendMoveToProgram(moveNum - 1, &first);
4840               }
4841             }
4842         }
4843 #endif
4844     }
4845
4846     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4847         /* If move comes from a remote source, animate it.  If it
4848            isn't remote, it will have already been animated. */
4849         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4850             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4851         }
4852         if (!pausing && appData.highlightLastMove) {
4853             SetHighlights(fromX, fromY, toX, toY);
4854         }
4855     }
4856
4857     /* Start the clocks */
4858     whiteFlag = blackFlag = FALSE;
4859     appData.clockMode = !(basetime == 0 && increment == 0);
4860     if (ticking == 0) {
4861       ics_clock_paused = TRUE;
4862       StopClocks();
4863     } else if (ticking == 1) {
4864       ics_clock_paused = FALSE;
4865     }
4866     if (gameMode == IcsIdle ||
4867         relation == RELATION_OBSERVING_STATIC ||
4868         relation == RELATION_EXAMINING ||
4869         ics_clock_paused)
4870       DisplayBothClocks();
4871     else
4872       StartClocks();
4873
4874     /* Display opponents and material strengths */
4875     if (gameInfo.variant != VariantBughouse &&
4876         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4877         if (tinyLayout || smallLayout) {
4878             if(gameInfo.variant == VariantNormal)
4879               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4880                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4881                     basetime, increment);
4882             else
4883               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4884                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4885                     basetime, increment, (int) gameInfo.variant);
4886         } else {
4887             if(gameInfo.variant == VariantNormal)
4888               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4889                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4890                     basetime, increment);
4891             else
4892               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4893                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4894                     basetime, increment, VariantName(gameInfo.variant));
4895         }
4896         DisplayTitle(str);
4897   if (appData.debugMode) {
4898     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4899   }
4900     }
4901
4902
4903     /* Display the board */
4904     if (!pausing && !appData.noGUI) {
4905
4906       if (appData.premove)
4907           if (!gotPremove ||
4908              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4909              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4910               ClearPremoveHighlights();
4911
4912       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4913         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4914       DrawPosition(j, boards[currentMove]);
4915
4916       DisplayMove(moveNum - 1);
4917       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4918             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4919               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4920         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4921       }
4922     }
4923
4924     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4925 #if ZIPPY
4926     if(bookHit) { // [HGM] book: simulate book reply
4927         static char bookMove[MSG_SIZ]; // a bit generous?
4928
4929         programStats.nodes = programStats.depth = programStats.time =
4930         programStats.score = programStats.got_only_move = 0;
4931         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4932
4933         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4934         strcat(bookMove, bookHit);
4935         HandleMachineMove(bookMove, &first);
4936     }
4937 #endif
4938 }
4939
4940 void
4941 GetMoveListEvent ()
4942 {
4943     char buf[MSG_SIZ];
4944     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4945         ics_getting_history = H_REQUESTED;
4946         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4947         SendToICS(buf);
4948     }
4949 }
4950
4951 void
4952 SendToBoth (char *msg)
4953 {   // to make it easy to keep two engines in step in dual analysis
4954     SendToProgram(msg, &first);
4955     if(second.analyzing) SendToProgram(msg, &second);
4956 }
4957
4958 void
4959 AnalysisPeriodicEvent (int force)
4960 {
4961     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4962          && !force) || !appData.periodicUpdates)
4963       return;
4964
4965     /* Send . command to Crafty to collect stats */
4966     SendToBoth(".\n");
4967
4968     /* Don't send another until we get a response (this makes
4969        us stop sending to old Crafty's which don't understand
4970        the "." command (sending illegal cmds resets node count & time,
4971        which looks bad)) */
4972     programStats.ok_to_send = 0;
4973 }
4974
4975 void
4976 ics_update_width (int new_width)
4977 {
4978         ics_printf("set width %d\n", new_width);
4979 }
4980
4981 void
4982 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4983 {
4984     char buf[MSG_SIZ];
4985
4986     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4987         // null move in variant where engine does not understand it (for analysis purposes)
4988         SendBoard(cps, moveNum + 1); // send position after move in stead.
4989         return;
4990     }
4991     if (cps->useUsermove) {
4992       SendToProgram("usermove ", cps);
4993     }
4994     if (cps->useSAN) {
4995       char *space;
4996       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4997         int len = space - parseList[moveNum];
4998         memcpy(buf, parseList[moveNum], len);
4999         buf[len++] = '\n';
5000         buf[len] = NULLCHAR;
5001       } else {
5002         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5003       }
5004       SendToProgram(buf, cps);
5005     } else {
5006       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5007         AlphaRank(moveList[moveNum], 4);
5008         SendToProgram(moveList[moveNum], cps);
5009         AlphaRank(moveList[moveNum], 4); // and back
5010       } else
5011       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5012        * the engine. It would be nice to have a better way to identify castle
5013        * moves here. */
5014       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5015                                                                          && cps->useOOCastle) {
5016         int fromX = moveList[moveNum][0] - AAA;
5017         int fromY = moveList[moveNum][1] - ONE;
5018         int toX = moveList[moveNum][2] - AAA;
5019         int toY = moveList[moveNum][3] - ONE;
5020         if((boards[moveNum][fromY][fromX] == WhiteKing
5021             && boards[moveNum][toY][toX] == WhiteRook)
5022            || (boards[moveNum][fromY][fromX] == BlackKing
5023                && boards[moveNum][toY][toX] == BlackRook)) {
5024           if(toX > fromX) SendToProgram("O-O\n", cps);
5025           else SendToProgram("O-O-O\n", cps);
5026         }
5027         else SendToProgram(moveList[moveNum], cps);
5028       } else
5029       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5030         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5031           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5032           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5033                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5034         } else
5035           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5036                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5037         SendToProgram(buf, cps);
5038       }
5039       else SendToProgram(moveList[moveNum], cps);
5040       /* End of additions by Tord */
5041     }
5042
5043     /* [HGM] setting up the opening has brought engine in force mode! */
5044     /*       Send 'go' if we are in a mode where machine should play. */
5045     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5046         (gameMode == TwoMachinesPlay   ||
5047 #if ZIPPY
5048          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5049 #endif
5050          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5051         SendToProgram("go\n", cps);
5052   if (appData.debugMode) {
5053     fprintf(debugFP, "(extra)\n");
5054   }
5055     }
5056     setboardSpoiledMachineBlack = 0;
5057 }
5058
5059 void
5060 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5061 {
5062     char user_move[MSG_SIZ];
5063     char suffix[4];
5064
5065     if(gameInfo.variant == VariantSChess && promoChar) {
5066         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5067         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5068     } else suffix[0] = NULLCHAR;
5069
5070     switch (moveType) {
5071       default:
5072         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5073                 (int)moveType, fromX, fromY, toX, toY);
5074         DisplayError(user_move + strlen("say "), 0);
5075         break;
5076       case WhiteKingSideCastle:
5077       case BlackKingSideCastle:
5078       case WhiteQueenSideCastleWild:
5079       case BlackQueenSideCastleWild:
5080       /* PUSH Fabien */
5081       case WhiteHSideCastleFR:
5082       case BlackHSideCastleFR:
5083       /* POP Fabien */
5084         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5085         break;
5086       case WhiteQueenSideCastle:
5087       case BlackQueenSideCastle:
5088       case WhiteKingSideCastleWild:
5089       case BlackKingSideCastleWild:
5090       /* PUSH Fabien */
5091       case WhiteASideCastleFR:
5092       case BlackASideCastleFR:
5093       /* POP Fabien */
5094         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5095         break;
5096       case WhiteNonPromotion:
5097       case BlackNonPromotion:
5098         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5099         break;
5100       case WhitePromotion:
5101       case BlackPromotion:
5102         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5103            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5104           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5105                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5106                 PieceToChar(WhiteFerz));
5107         else if(gameInfo.variant == VariantGreat)
5108           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5109                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5110                 PieceToChar(WhiteMan));
5111         else
5112           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5113                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5114                 promoChar);
5115         break;
5116       case WhiteDrop:
5117       case BlackDrop:
5118       drop:
5119         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5120                  ToUpper(PieceToChar((ChessSquare) fromX)),
5121                  AAA + toX, ONE + toY);
5122         break;
5123       case IllegalMove:  /* could be a variant we don't quite understand */
5124         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5125       case NormalMove:
5126       case WhiteCapturesEnPassant:
5127       case BlackCapturesEnPassant:
5128         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5129                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5130         break;
5131     }
5132     SendToICS(user_move);
5133     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5134         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5135 }
5136
5137 void
5138 UploadGameEvent ()
5139 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5140     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5141     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5142     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5143       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5144       return;
5145     }
5146     if(gameMode != IcsExamining) { // is this ever not the case?
5147         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5148
5149         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5150           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5151         } else { // on FICS we must first go to general examine mode
5152           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5153         }
5154         if(gameInfo.variant != VariantNormal) {
5155             // try figure out wild number, as xboard names are not always valid on ICS
5156             for(i=1; i<=36; i++) {
5157               snprintf(buf, MSG_SIZ, "wild/%d", i);
5158                 if(StringToVariant(buf) == gameInfo.variant) break;
5159             }
5160             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5161             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5162             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5163         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5164         SendToICS(ics_prefix);
5165         SendToICS(buf);
5166         if(startedFromSetupPosition || backwardMostMove != 0) {
5167           fen = PositionToFEN(backwardMostMove, NULL, 1);
5168           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5169             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5170             SendToICS(buf);
5171           } else { // FICS: everything has to set by separate bsetup commands
5172             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5173             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5174             SendToICS(buf);
5175             if(!WhiteOnMove(backwardMostMove)) {
5176                 SendToICS("bsetup tomove black\n");
5177             }
5178             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5179             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5180             SendToICS(buf);
5181             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5182             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5183             SendToICS(buf);
5184             i = boards[backwardMostMove][EP_STATUS];
5185             if(i >= 0) { // set e.p.
5186               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5187                 SendToICS(buf);
5188             }
5189             bsetup++;
5190           }
5191         }
5192       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5193             SendToICS("bsetup done\n"); // switch to normal examining.
5194     }
5195     for(i = backwardMostMove; i<last; i++) {
5196         char buf[20];
5197         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5198         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5199             int len = strlen(moveList[i]);
5200             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5201             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5202         }
5203         SendToICS(buf);
5204     }
5205     SendToICS(ics_prefix);
5206     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5207 }
5208
5209 void
5210 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5211 {
5212     if (rf == DROP_RANK) {
5213       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5214       sprintf(move, "%c@%c%c\n",
5215                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5216     } else {
5217         if (promoChar == 'x' || promoChar == NULLCHAR) {
5218           sprintf(move, "%c%c%c%c\n",
5219                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5220         } else {
5221             sprintf(move, "%c%c%c%c%c\n",
5222                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5223         }
5224     }
5225 }
5226
5227 void
5228 ProcessICSInitScript (FILE *f)
5229 {
5230     char buf[MSG_SIZ];
5231
5232     while (fgets(buf, MSG_SIZ, f)) {
5233         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5234     }
5235
5236     fclose(f);
5237 }
5238
5239
5240 static int lastX, lastY, selectFlag, dragging;
5241
5242 void
5243 Sweep (int step)
5244 {
5245     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5246     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5247     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5248     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5249     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5250     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5251     do {
5252         promoSweep -= step;
5253         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5254         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5255         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5256         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5257         if(!step) step = -1;
5258     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5259             appData.testLegality && (promoSweep == king ||
5260             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5261     if(toX >= 0) {
5262         int victim = boards[currentMove][toY][toX];
5263         boards[currentMove][toY][toX] = promoSweep;
5264         DrawPosition(FALSE, boards[currentMove]);
5265         boards[currentMove][toY][toX] = victim;
5266     } else
5267     ChangeDragPiece(promoSweep);
5268 }
5269
5270 int
5271 PromoScroll (int x, int y)
5272 {
5273   int step = 0;
5274
5275   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5276   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5277   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5278   if(!step) return FALSE;
5279   lastX = x; lastY = y;
5280   if((promoSweep < BlackPawn) == flipView) step = -step;
5281   if(step > 0) selectFlag = 1;
5282   if(!selectFlag) Sweep(step);
5283   return FALSE;
5284 }
5285
5286 void
5287 NextPiece (int step)
5288 {
5289     ChessSquare piece = boards[currentMove][toY][toX];
5290     do {
5291         pieceSweep -= step;
5292         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5293         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5294         if(!step) step = -1;
5295     } while(PieceToChar(pieceSweep) == '.');
5296     boards[currentMove][toY][toX] = pieceSweep;
5297     DrawPosition(FALSE, boards[currentMove]);
5298     boards[currentMove][toY][toX] = piece;
5299 }
5300 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5301 void
5302 AlphaRank (char *move, int n)
5303 {
5304 //    char *p = move, c; int x, y;
5305
5306     if (appData.debugMode) {
5307         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5308     }
5309
5310     if(move[1]=='*' &&
5311        move[2]>='0' && move[2]<='9' &&
5312        move[3]>='a' && move[3]<='x'    ) {
5313         move[1] = '@';
5314         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5315         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5316     } else
5317     if(move[0]>='0' && move[0]<='9' &&
5318        move[1]>='a' && move[1]<='x' &&
5319        move[2]>='0' && move[2]<='9' &&
5320        move[3]>='a' && move[3]<='x'    ) {
5321         /* input move, Shogi -> normal */
5322         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5323         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5324         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5325         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5326     } else
5327     if(move[1]=='@' &&
5328        move[3]>='0' && move[3]<='9' &&
5329        move[2]>='a' && move[2]<='x'    ) {
5330         move[1] = '*';
5331         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5332         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5333     } else
5334     if(
5335        move[0]>='a' && move[0]<='x' &&
5336        move[3]>='0' && move[3]<='9' &&
5337        move[2]>='a' && move[2]<='x'    ) {
5338          /* output move, normal -> Shogi */
5339         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5340         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5341         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5342         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5343         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5344     }
5345     if (appData.debugMode) {
5346         fprintf(debugFP, "   out = '%s'\n", move);
5347     }
5348 }
5349
5350 char yy_textstr[8000];
5351
5352 /* Parser for moves from gnuchess, ICS, or user typein box */
5353 Boolean
5354 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5355 {
5356     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5357
5358     switch (*moveType) {
5359       case WhitePromotion:
5360       case BlackPromotion:
5361       case WhiteNonPromotion:
5362       case BlackNonPromotion:
5363       case NormalMove:
5364       case WhiteCapturesEnPassant:
5365       case BlackCapturesEnPassant:
5366       case WhiteKingSideCastle:
5367       case WhiteQueenSideCastle:
5368       case BlackKingSideCastle:
5369       case BlackQueenSideCastle:
5370       case WhiteKingSideCastleWild:
5371       case WhiteQueenSideCastleWild:
5372       case BlackKingSideCastleWild:
5373       case BlackQueenSideCastleWild:
5374       /* Code added by Tord: */
5375       case WhiteHSideCastleFR:
5376       case WhiteASideCastleFR:
5377       case BlackHSideCastleFR:
5378       case BlackASideCastleFR:
5379       /* End of code added by Tord */
5380       case IllegalMove:         /* bug or odd chess variant */
5381         *fromX = currentMoveString[0] - AAA;
5382         *fromY = currentMoveString[1] - ONE;
5383         *toX = currentMoveString[2] - AAA;
5384         *toY = currentMoveString[3] - ONE;
5385         *promoChar = currentMoveString[4];
5386         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5387             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5388     if (appData.debugMode) {
5389         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5390     }
5391             *fromX = *fromY = *toX = *toY = 0;
5392             return FALSE;
5393         }
5394         if (appData.testLegality) {
5395           return (*moveType != IllegalMove);
5396         } else {
5397           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5398                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5399         }
5400
5401       case WhiteDrop:
5402       case BlackDrop:
5403         *fromX = *moveType == WhiteDrop ?
5404           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5405           (int) CharToPiece(ToLower(currentMoveString[0]));
5406         *fromY = DROP_RANK;
5407         *toX = currentMoveString[2] - AAA;
5408         *toY = currentMoveString[3] - ONE;
5409         *promoChar = NULLCHAR;
5410         return TRUE;
5411
5412       case AmbiguousMove:
5413       case ImpossibleMove:
5414       case EndOfFile:
5415       case ElapsedTime:
5416       case Comment:
5417       case PGNTag:
5418       case NAG:
5419       case WhiteWins:
5420       case BlackWins:
5421       case GameIsDrawn:
5422       default:
5423     if (appData.debugMode) {
5424         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5425     }
5426         /* bug? */
5427         *fromX = *fromY = *toX = *toY = 0;
5428         *promoChar = NULLCHAR;
5429         return FALSE;
5430     }
5431 }
5432
5433 Boolean pushed = FALSE;
5434 char *lastParseAttempt;
5435
5436 void
5437 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5439   int fromX, fromY, toX, toY; char promoChar;
5440   ChessMove moveType;
5441   Boolean valid;
5442   int nr = 0;
5443
5444   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5445   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5446     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5447     pushed = TRUE;
5448   }
5449   endPV = forwardMostMove;
5450   do {
5451     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5452     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5453     lastParseAttempt = pv;
5454     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5455     if(!valid && nr == 0 &&
5456        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5457         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5458         // Hande case where played move is different from leading PV move
5459         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5460         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5461         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5462         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5463           endPV += 2; // if position different, keep this
5464           moveList[endPV-1][0] = fromX + AAA;
5465           moveList[endPV-1][1] = fromY + ONE;
5466           moveList[endPV-1][2] = toX + AAA;
5467           moveList[endPV-1][3] = toY + ONE;
5468           parseList[endPV-1][0] = NULLCHAR;
5469           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5470         }
5471       }
5472     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5473     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5474     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5475     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5476         valid++; // allow comments in PV
5477         continue;
5478     }
5479     nr++;
5480     if(endPV+1 > framePtr) break; // no space, truncate
5481     if(!valid) break;
5482     endPV++;
5483     CopyBoard(boards[endPV], boards[endPV-1]);
5484     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5485     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5486     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5487     CoordsToAlgebraic(boards[endPV - 1],
5488                              PosFlags(endPV - 1),
5489                              fromY, fromX, toY, toX, promoChar,
5490                              parseList[endPV - 1]);
5491   } while(valid);
5492   if(atEnd == 2) return; // used hidden, for PV conversion
5493   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(TRUE, boards[currentMove]);
5498 }
5499
5500 int
5501 MultiPV (ChessProgramState *cps)
5502 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5503         int i;
5504         for(i=0; i<cps->nrOptions; i++)
5505             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5506                 return i;
5507         return -1;
5508 }
5509
5510 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5511
5512 Boolean
5513 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5514 {
5515         int startPV, multi, lineStart, origIndex = index;
5516         char *p, buf2[MSG_SIZ];
5517         ChessProgramState *cps = (pane ? &second : &first);
5518
5519         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5520         lastX = x; lastY = y;
5521         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5522         lineStart = startPV = index;
5523         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5524         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5525         index = startPV;
5526         do{ while(buf[index] && buf[index] != '\n') index++;
5527         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5528         buf[index] = 0;
5529         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5530                 int n = cps->option[multi].value;
5531                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5532                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5533                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5534                 cps->option[multi].value = n;
5535                 *start = *end = 0;
5536                 return FALSE;
5537         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5538                 ExcludeClick(origIndex - lineStart);
5539                 return FALSE;
5540         }
5541         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5542         *start = startPV; *end = index-1;
5543         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5544         return TRUE;
5545 }
5546
5547 char *
5548 PvToSAN (char *pv)
5549 {
5550         static char buf[10*MSG_SIZ];
5551         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5552         *buf = NULLCHAR;
5553         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5554         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5555         for(i = forwardMostMove; i<endPV; i++){
5556             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5557             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5558             k += strlen(buf+k);
5559         }
5560         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5561         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5562         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5563         endPV = savedEnd;
5564         return buf;
5565 }
5566
5567 Boolean
5568 LoadPV (int x, int y)
5569 { // called on right mouse click to load PV
5570   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5571   lastX = x; lastY = y;
5572   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5573   extendGame = FALSE;
5574   return TRUE;
5575 }
5576
5577 void
5578 UnLoadPV ()
5579 {
5580   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5581   if(endPV < 0) return;
5582   if(appData.autoCopyPV) CopyFENToClipboard();
5583   endPV = -1;
5584   if(extendGame && currentMove > forwardMostMove) {
5585         Boolean saveAnimate = appData.animate;
5586         if(pushed) {
5587             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5588                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5589             } else storedGames--; // abandon shelved tail of original game
5590         }
5591         pushed = FALSE;
5592         forwardMostMove = currentMove;
5593         currentMove = oldFMM;
5594         appData.animate = FALSE;
5595         ToNrEvent(forwardMostMove);
5596         appData.animate = saveAnimate;
5597   }
5598   currentMove = forwardMostMove;
5599   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5600   ClearPremoveHighlights();
5601   DrawPosition(TRUE, boards[currentMove]);
5602 }
5603
5604 void
5605 MovePV (int x, int y, int h)
5606 { // step through PV based on mouse coordinates (called on mouse move)
5607   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5608
5609   // we must somehow check if right button is still down (might be released off board!)
5610   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5611   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5612   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5613   if(!step) return;
5614   lastX = x; lastY = y;
5615
5616   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5617   if(endPV < 0) return;
5618   if(y < margin) step = 1; else
5619   if(y > h - margin) step = -1;
5620   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5621   currentMove += step;
5622   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5623   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5624                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5625   DrawPosition(FALSE, boards[currentMove]);
5626 }
5627
5628
5629 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5630 // All positions will have equal probability, but the current method will not provide a unique
5631 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5632 #define DARK 1
5633 #define LITE 2
5634 #define ANY 3
5635
5636 int squaresLeft[4];
5637 int piecesLeft[(int)BlackPawn];
5638 int seed, nrOfShuffles;
5639
5640 void
5641 GetPositionNumber ()
5642 {       // sets global variable seed
5643         int i;
5644
5645         seed = appData.defaultFrcPosition;
5646         if(seed < 0) { // randomize based on time for negative FRC position numbers
5647                 for(i=0; i<50; i++) seed += random();
5648                 seed = random() ^ random() >> 8 ^ random() << 8;
5649                 if(seed<0) seed = -seed;
5650         }
5651 }
5652
5653 int
5654 put (Board board, int pieceType, int rank, int n, int shade)
5655 // put the piece on the (n-1)-th empty squares of the given shade
5656 {
5657         int i;
5658
5659         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5660                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5661                         board[rank][i] = (ChessSquare) pieceType;
5662                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5663                         squaresLeft[ANY]--;
5664                         piecesLeft[pieceType]--;
5665                         return i;
5666                 }
5667         }
5668         return -1;
5669 }
5670
5671
5672 void
5673 AddOnePiece (Board board, int pieceType, int rank, int shade)
5674 // calculate where the next piece goes, (any empty square), and put it there
5675 {
5676         int i;
5677
5678         i = seed % squaresLeft[shade];
5679         nrOfShuffles *= squaresLeft[shade];
5680         seed /= squaresLeft[shade];
5681         put(board, pieceType, rank, i, shade);
5682 }
5683
5684 void
5685 AddTwoPieces (Board board, int pieceType, int rank)
5686 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5687 {
5688         int i, n=squaresLeft[ANY], j=n-1, k;
5689
5690         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5691         i = seed % k;  // pick one
5692         nrOfShuffles *= k;
5693         seed /= k;
5694         while(i >= j) i -= j--;
5695         j = n - 1 - j; i += j;
5696         put(board, pieceType, rank, j, ANY);
5697         put(board, pieceType, rank, i, ANY);
5698 }
5699
5700 void
5701 SetUpShuffle (Board board, int number)
5702 {
5703         int i, p, first=1;
5704
5705         GetPositionNumber(); nrOfShuffles = 1;
5706
5707         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5708         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5709         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5710
5711         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5712
5713         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5714             p = (int) board[0][i];
5715             if(p < (int) BlackPawn) piecesLeft[p] ++;
5716             board[0][i] = EmptySquare;
5717         }
5718
5719         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5720             // shuffles restricted to allow normal castling put KRR first
5721             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5722                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5723             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5724                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5725             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5726                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5727             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5728                 put(board, WhiteRook, 0, 0, ANY);
5729             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5730         }
5731
5732         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5733             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5734             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5735                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5736                 while(piecesLeft[p] >= 2) {
5737                     AddOnePiece(board, p, 0, LITE);
5738                     AddOnePiece(board, p, 0, DARK);
5739                 }
5740                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5741             }
5742
5743         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5744             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5745             // but we leave King and Rooks for last, to possibly obey FRC restriction
5746             if(p == (int)WhiteRook) continue;
5747             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5748             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5749         }
5750
5751         // now everything is placed, except perhaps King (Unicorn) and Rooks
5752
5753         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5754             // Last King gets castling rights
5755             while(piecesLeft[(int)WhiteUnicorn]) {
5756                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5757                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5758             }
5759
5760             while(piecesLeft[(int)WhiteKing]) {
5761                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5762                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5763             }
5764
5765
5766         } else {
5767             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5768             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5769         }
5770
5771         // Only Rooks can be left; simply place them all
5772         while(piecesLeft[(int)WhiteRook]) {
5773                 i = put(board, WhiteRook, 0, 0, ANY);
5774                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5775                         if(first) {
5776                                 first=0;
5777                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5778                         }
5779                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5780                 }
5781         }
5782         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5783             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5784         }
5785
5786         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5787 }
5788
5789 int
5790 SetCharTable (char *table, const char * map)
5791 /* [HGM] moved here from winboard.c because of its general usefulness */
5792 /*       Basically a safe strcpy that uses the last character as King */
5793 {
5794     int result = FALSE; int NrPieces;
5795
5796     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5797                     && NrPieces >= 12 && !(NrPieces&1)) {
5798         int i; /* [HGM] Accept even length from 12 to 34 */
5799
5800         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5801         for( i=0; i<NrPieces/2-1; i++ ) {
5802             table[i] = map[i];
5803             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5804         }
5805         table[(int) WhiteKing]  = map[NrPieces/2-1];
5806         table[(int) BlackKing]  = map[NrPieces-1];
5807
5808         result = TRUE;
5809     }
5810
5811     return result;
5812 }
5813
5814 void
5815 Prelude (Board board)
5816 {       // [HGM] superchess: random selection of exo-pieces
5817         int i, j, k; ChessSquare p;
5818         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5819
5820         GetPositionNumber(); // use FRC position number
5821
5822         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5823             SetCharTable(pieceToChar, appData.pieceToCharTable);
5824             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5825                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5826         }
5827
5828         j = seed%4;                 seed /= 4;
5829         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5830         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5831         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5832         j = seed%3 + (seed%3 >= j); seed /= 3;
5833         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5834         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5835         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5836         j = seed%3;                 seed /= 3;
5837         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5838         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5839         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5840         j = seed%2 + (seed%2 >= j); seed /= 2;
5841         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5842         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5843         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5844         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5845         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5846         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5847         put(board, exoPieces[0],    0, 0, ANY);
5848         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5849 }
5850
5851 void
5852 InitPosition (int redraw)
5853 {
5854     ChessSquare (* pieces)[BOARD_FILES];
5855     int i, j, pawnRow, overrule,
5856     oldx = gameInfo.boardWidth,
5857     oldy = gameInfo.boardHeight,
5858     oldh = gameInfo.holdingsWidth;
5859     static int oldv;
5860
5861     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5862
5863     /* [AS] Initialize pv info list [HGM] and game status */
5864     {
5865         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5866             pvInfoList[i].depth = 0;
5867             boards[i][EP_STATUS] = EP_NONE;
5868             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5869         }
5870
5871         initialRulePlies = 0; /* 50-move counter start */
5872
5873         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5874         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5875     }
5876
5877
5878     /* [HGM] logic here is completely changed. In stead of full positions */
5879     /* the initialized data only consist of the two backranks. The switch */
5880     /* selects which one we will use, which is than copied to the Board   */
5881     /* initialPosition, which for the rest is initialized by Pawns and    */
5882     /* empty squares. This initial position is then copied to boards[0],  */
5883     /* possibly after shuffling, so that it remains available.            */
5884
5885     gameInfo.holdingsWidth = 0; /* default board sizes */
5886     gameInfo.boardWidth    = 8;
5887     gameInfo.boardHeight   = 8;
5888     gameInfo.holdingsSize  = 0;
5889     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5890     for(i=0; i<BOARD_FILES-2; i++)
5891       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5892     initialPosition[EP_STATUS] = EP_NONE;
5893     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5894     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5895          SetCharTable(pieceNickName, appData.pieceNickNames);
5896     else SetCharTable(pieceNickName, "............");
5897     pieces = FIDEArray;
5898
5899     switch (gameInfo.variant) {
5900     case VariantFischeRandom:
5901       shuffleOpenings = TRUE;
5902     default:
5903       break;
5904     case VariantShatranj:
5905       pieces = ShatranjArray;
5906       nrCastlingRights = 0;
5907       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5908       break;
5909     case VariantMakruk:
5910       pieces = makrukArray;
5911       nrCastlingRights = 0;
5912       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5913       break;
5914     case VariantASEAN:
5915       pieces = aseanArray;
5916       nrCastlingRights = 0;
5917       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5918       break;
5919     case VariantTwoKings:
5920       pieces = twoKingsArray;
5921       break;
5922     case VariantGrand:
5923       pieces = GrandArray;
5924       nrCastlingRights = 0;
5925       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5926       gameInfo.boardWidth = 10;
5927       gameInfo.boardHeight = 10;
5928       gameInfo.holdingsSize = 7;
5929       break;
5930     case VariantCapaRandom:
5931       shuffleOpenings = TRUE;
5932     case VariantCapablanca:
5933       pieces = CapablancaArray;
5934       gameInfo.boardWidth = 10;
5935       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5936       break;
5937     case VariantGothic:
5938       pieces = GothicArray;
5939       gameInfo.boardWidth = 10;
5940       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5941       break;
5942     case VariantSChess:
5943       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5944       gameInfo.holdingsSize = 7;
5945       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5946       break;
5947     case VariantJanus:
5948       pieces = JanusArray;
5949       gameInfo.boardWidth = 10;
5950       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5951       nrCastlingRights = 6;
5952         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5953         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5954         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5955         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5956         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5957         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5958       break;
5959     case VariantFalcon:
5960       pieces = FalconArray;
5961       gameInfo.boardWidth = 10;
5962       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5963       break;
5964     case VariantXiangqi:
5965       pieces = XiangqiArray;
5966       gameInfo.boardWidth  = 9;
5967       gameInfo.boardHeight = 10;
5968       nrCastlingRights = 0;
5969       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5970       break;
5971     case VariantShogi:
5972       pieces = ShogiArray;
5973       gameInfo.boardWidth  = 9;
5974       gameInfo.boardHeight = 9;
5975       gameInfo.holdingsSize = 7;
5976       nrCastlingRights = 0;
5977       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5978       break;
5979     case VariantCourier:
5980       pieces = CourierArray;
5981       gameInfo.boardWidth  = 12;
5982       nrCastlingRights = 0;
5983       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5984       break;
5985     case VariantKnightmate:
5986       pieces = KnightmateArray;
5987       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5988       break;
5989     case VariantSpartan:
5990       pieces = SpartanArray;
5991       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5992       break;
5993     case VariantFairy:
5994       pieces = fairyArray;
5995       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5996       break;
5997     case VariantGreat:
5998       pieces = GreatArray;
5999       gameInfo.boardWidth = 10;
6000       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6001       gameInfo.holdingsSize = 8;
6002       break;
6003     case VariantSuper:
6004       pieces = FIDEArray;
6005       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6006       gameInfo.holdingsSize = 8;
6007       startedFromSetupPosition = TRUE;
6008       break;
6009     case VariantCrazyhouse:
6010     case VariantBughouse:
6011       pieces = FIDEArray;
6012       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6013       gameInfo.holdingsSize = 5;
6014       break;
6015     case VariantWildCastle:
6016       pieces = FIDEArray;
6017       /* !!?shuffle with kings guaranteed to be on d or e file */
6018       shuffleOpenings = 1;
6019       break;
6020     case VariantNoCastle:
6021       pieces = FIDEArray;
6022       nrCastlingRights = 0;
6023       /* !!?unconstrained back-rank shuffle */
6024       shuffleOpenings = 1;
6025       break;
6026     }
6027
6028     overrule = 0;
6029     if(appData.NrFiles >= 0) {
6030         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6031         gameInfo.boardWidth = appData.NrFiles;
6032     }
6033     if(appData.NrRanks >= 0) {
6034         gameInfo.boardHeight = appData.NrRanks;
6035     }
6036     if(appData.holdingsSize >= 0) {
6037         i = appData.holdingsSize;
6038         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6039         gameInfo.holdingsSize = i;
6040     }
6041     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6042     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6043         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6044
6045     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6046     if(pawnRow < 1) pawnRow = 1;
6047     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6048
6049     /* User pieceToChar list overrules defaults */
6050     if(appData.pieceToCharTable != NULL)
6051         SetCharTable(pieceToChar, appData.pieceToCharTable);
6052
6053     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6054
6055         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6056             s = (ChessSquare) 0; /* account holding counts in guard band */
6057         for( i=0; i<BOARD_HEIGHT; i++ )
6058             initialPosition[i][j] = s;
6059
6060         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6061         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6062         initialPosition[pawnRow][j] = WhitePawn;
6063         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6064         if(gameInfo.variant == VariantXiangqi) {
6065             if(j&1) {
6066                 initialPosition[pawnRow][j] =
6067                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6068                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6069                    initialPosition[2][j] = WhiteCannon;
6070                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6071                 }
6072             }
6073         }
6074         if(gameInfo.variant == VariantGrand) {
6075             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6076                initialPosition[0][j] = WhiteRook;
6077                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6078             }
6079         }
6080         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6081     }
6082     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6083
6084             j=BOARD_LEFT+1;
6085             initialPosition[1][j] = WhiteBishop;
6086             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6087             j=BOARD_RGHT-2;
6088             initialPosition[1][j] = WhiteRook;
6089             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6090     }
6091
6092     if( nrCastlingRights == -1) {
6093         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6094         /*       This sets default castling rights from none to normal corners   */
6095         /* Variants with other castling rights must set them themselves above    */
6096         nrCastlingRights = 6;
6097
6098         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6099         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6100         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6101         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6102         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6103         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6104      }
6105
6106      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6107      if(gameInfo.variant == VariantGreat) { // promotion commoners
6108         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6109         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6110         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6111         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6112      }
6113      if( gameInfo.variant == VariantSChess ) {
6114       initialPosition[1][0] = BlackMarshall;
6115       initialPosition[2][0] = BlackAngel;
6116       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6117       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6118       initialPosition[1][1] = initialPosition[2][1] =
6119       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6120      }
6121   if (appData.debugMode) {
6122     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6123   }
6124     if(shuffleOpenings) {
6125         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6126         startedFromSetupPosition = TRUE;
6127     }
6128     if(startedFromPositionFile) {
6129       /* [HGM] loadPos: use PositionFile for every new game */
6130       CopyBoard(initialPosition, filePosition);
6131       for(i=0; i<nrCastlingRights; i++)
6132           initialRights[i] = filePosition[CASTLING][i];
6133       startedFromSetupPosition = TRUE;
6134     }
6135
6136     CopyBoard(boards[0], initialPosition);
6137
6138     if(oldx != gameInfo.boardWidth ||
6139        oldy != gameInfo.boardHeight ||
6140        oldv != gameInfo.variant ||
6141        oldh != gameInfo.holdingsWidth
6142                                          )
6143             InitDrawingSizes(-2 ,0);
6144
6145     oldv = gameInfo.variant;
6146     if (redraw)
6147       DrawPosition(TRUE, boards[currentMove]);
6148 }
6149
6150 void
6151 SendBoard (ChessProgramState *cps, int moveNum)
6152 {
6153     char message[MSG_SIZ];
6154
6155     if (cps->useSetboard) {
6156       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6157       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6158       SendToProgram(message, cps);
6159       free(fen);
6160
6161     } else {
6162       ChessSquare *bp;
6163       int i, j, left=0, right=BOARD_WIDTH;
6164       /* Kludge to set black to move, avoiding the troublesome and now
6165        * deprecated "black" command.
6166        */
6167       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6168         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6169
6170       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6171
6172       SendToProgram("edit\n", cps);
6173       SendToProgram("#\n", cps);
6174       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6175         bp = &boards[moveNum][i][left];
6176         for (j = left; j < right; j++, bp++) {
6177           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6178           if ((int) *bp < (int) BlackPawn) {
6179             if(j == BOARD_RGHT+1)
6180                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6181             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6182             if(message[0] == '+' || message[0] == '~') {
6183               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6184                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6185                         AAA + j, ONE + i);
6186             }
6187             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6188                 message[1] = BOARD_RGHT   - 1 - j + '1';
6189                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6190             }
6191             SendToProgram(message, cps);
6192           }
6193         }
6194       }
6195
6196       SendToProgram("c\n", cps);
6197       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6198         bp = &boards[moveNum][i][left];
6199         for (j = left; j < right; j++, bp++) {
6200           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6201           if (((int) *bp != (int) EmptySquare)
6202               && ((int) *bp >= (int) BlackPawn)) {
6203             if(j == BOARD_LEFT-2)
6204                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6205             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6206                     AAA + j, ONE + i);
6207             if(message[0] == '+' || message[0] == '~') {
6208               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6209                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6210                         AAA + j, ONE + i);
6211             }
6212             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6213                 message[1] = BOARD_RGHT   - 1 - j + '1';
6214                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6215             }
6216             SendToProgram(message, cps);
6217           }
6218         }
6219       }
6220
6221       SendToProgram(".\n", cps);
6222     }
6223     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6224 }
6225
6226 char exclusionHeader[MSG_SIZ];
6227 int exCnt, excludePtr;
6228 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6229 static Exclusion excluTab[200];
6230 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6231
6232 static void
6233 WriteMap (int s)
6234 {
6235     int j;
6236     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6237     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6238 }
6239
6240 static void
6241 ClearMap ()
6242 {
6243     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6244     excludePtr = 24; exCnt = 0;
6245     WriteMap(0);
6246 }
6247
6248 static void
6249 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6250 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6251     char buf[2*MOVE_LEN], *p;
6252     Exclusion *e = excluTab;
6253     int i;
6254     for(i=0; i<exCnt; i++)
6255         if(e[i].ff == fromX && e[i].fr == fromY &&
6256            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6257     if(i == exCnt) { // was not in exclude list; add it
6258         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6259         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6260             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6261             return; // abort
6262         }
6263         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6264         excludePtr++; e[i].mark = excludePtr++;
6265         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6266         exCnt++;
6267     }
6268     exclusionHeader[e[i].mark] = state;
6269 }
6270
6271 static int
6272 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6273 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6274     char buf[MSG_SIZ];
6275     int j, k;
6276     ChessMove moveType;
6277     if((signed char)promoChar == -1) { // kludge to indicate best move
6278         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6279             return 1; // if unparsable, abort
6280     }
6281     // update exclusion map (resolving toggle by consulting existing state)
6282     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6283     j = k%8; k >>= 3;
6284     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6285     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6286          excludeMap[k] |=   1<<j;
6287     else excludeMap[k] &= ~(1<<j);
6288     // update header
6289     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6290     // inform engine
6291     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6292     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6293     SendToBoth(buf);
6294     return (state == '+');
6295 }
6296
6297 static void
6298 ExcludeClick (int index)
6299 {
6300     int i, j;
6301     Exclusion *e = excluTab;
6302     if(index < 25) { // none, best or tail clicked
6303         if(index < 13) { // none: include all
6304             WriteMap(0); // clear map
6305             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6306             SendToBoth("include all\n"); // and inform engine
6307         } else if(index > 18) { // tail
6308             if(exclusionHeader[19] == '-') { // tail was excluded
6309                 SendToBoth("include all\n");
6310                 WriteMap(0); // clear map completely
6311                 // now re-exclude selected moves
6312                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6313                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6314             } else { // tail was included or in mixed state
6315                 SendToBoth("exclude all\n");
6316                 WriteMap(0xFF); // fill map completely
6317                 // now re-include selected moves
6318                 j = 0; // count them
6319                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6320                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6321                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6322             }
6323         } else { // best
6324             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6325         }
6326     } else {
6327         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6328             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6329             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6330             break;
6331         }
6332     }
6333 }
6334
6335 ChessSquare
6336 DefaultPromoChoice (int white)
6337 {
6338     ChessSquare result;
6339     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6340        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6341         result = WhiteFerz; // no choice
6342     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6343         result= WhiteKing; // in Suicide Q is the last thing we want
6344     else if(gameInfo.variant == VariantSpartan)
6345         result = white ? WhiteQueen : WhiteAngel;
6346     else result = WhiteQueen;
6347     if(!white) result = WHITE_TO_BLACK result;
6348     return result;
6349 }
6350
6351 static int autoQueen; // [HGM] oneclick
6352
6353 int
6354 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6355 {
6356     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6357     /* [HGM] add Shogi promotions */
6358     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6359     ChessSquare piece;
6360     ChessMove moveType;
6361     Boolean premove;
6362
6363     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6364     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6365
6366     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6367       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6368         return FALSE;
6369
6370     piece = boards[currentMove][fromY][fromX];
6371     if(gameInfo.variant == VariantShogi) {
6372         promotionZoneSize = BOARD_HEIGHT/3;
6373         highestPromotingPiece = (int)WhiteFerz;
6374     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6375         promotionZoneSize = 3;
6376     }
6377
6378     // Treat Lance as Pawn when it is not representing Amazon
6379     if(gameInfo.variant != VariantSuper) {
6380         if(piece == WhiteLance) piece = WhitePawn; else
6381         if(piece == BlackLance) piece = BlackPawn;
6382     }
6383
6384     // next weed out all moves that do not touch the promotion zone at all
6385     if((int)piece >= BlackPawn) {
6386         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6387              return FALSE;
6388         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6389     } else {
6390         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6391            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6392     }
6393
6394     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6395
6396     // weed out mandatory Shogi promotions
6397     if(gameInfo.variant == VariantShogi) {
6398         if(piece >= BlackPawn) {
6399             if(toY == 0 && piece == BlackPawn ||
6400                toY == 0 && piece == BlackQueen ||
6401                toY <= 1 && piece == BlackKnight) {
6402                 *promoChoice = '+';
6403                 return FALSE;
6404             }
6405         } else {
6406             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6407                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6408                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6409                 *promoChoice = '+';
6410                 return FALSE;
6411             }
6412         }
6413     }
6414
6415     // weed out obviously illegal Pawn moves
6416     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6417         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6418         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6419         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6420         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6421         // note we are not allowed to test for valid (non-)capture, due to premove
6422     }
6423
6424     // we either have a choice what to promote to, or (in Shogi) whether to promote
6425     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6426        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6427         *promoChoice = PieceToChar(BlackFerz);  // no choice
6428         return FALSE;
6429     }
6430     // no sense asking what we must promote to if it is going to explode...
6431     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6432         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6433         return FALSE;
6434     }
6435     // give caller the default choice even if we will not make it
6436     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6437     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6438     if(        sweepSelect && gameInfo.variant != VariantGreat
6439                            && gameInfo.variant != VariantGrand
6440                            && gameInfo.variant != VariantSuper) return FALSE;
6441     if(autoQueen) return FALSE; // predetermined
6442
6443     // suppress promotion popup on illegal moves that are not premoves
6444     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6445               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6446     if(appData.testLegality && !premove) {
6447         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6448                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6449         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6450             return FALSE;
6451     }
6452
6453     return TRUE;
6454 }
6455
6456 int
6457 InPalace (int row, int column)
6458 {   /* [HGM] for Xiangqi */
6459     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6460          column < (BOARD_WIDTH + 4)/2 &&
6461          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6462     return FALSE;
6463 }
6464
6465 int
6466 PieceForSquare (int x, int y)
6467 {
6468   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6469      return -1;
6470   else
6471      return boards[currentMove][y][x];
6472 }
6473
6474 int
6475 OKToStartUserMove (int x, int y)
6476 {
6477     ChessSquare from_piece;
6478     int white_piece;
6479
6480     if (matchMode) return FALSE;
6481     if (gameMode == EditPosition) return TRUE;
6482
6483     if (x >= 0 && y >= 0)
6484       from_piece = boards[currentMove][y][x];
6485     else
6486       from_piece = EmptySquare;
6487
6488     if (from_piece == EmptySquare) return FALSE;
6489
6490     white_piece = (int)from_piece >= (int)WhitePawn &&
6491       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6492
6493     switch (gameMode) {
6494       case AnalyzeFile:
6495       case TwoMachinesPlay:
6496       case EndOfGame:
6497         return FALSE;
6498
6499       case IcsObserving:
6500       case IcsIdle:
6501         return FALSE;
6502
6503       case MachinePlaysWhite:
6504       case IcsPlayingBlack:
6505         if (appData.zippyPlay) return FALSE;
6506         if (white_piece) {
6507             DisplayMoveError(_("You are playing Black"));
6508             return FALSE;
6509         }
6510         break;
6511
6512       case MachinePlaysBlack:
6513       case IcsPlayingWhite:
6514         if (appData.zippyPlay) return FALSE;
6515         if (!white_piece) {
6516             DisplayMoveError(_("You are playing White"));
6517             return FALSE;
6518         }
6519         break;
6520
6521       case PlayFromGameFile:
6522             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6523       case EditGame:
6524         if (!white_piece && WhiteOnMove(currentMove)) {
6525             DisplayMoveError(_("It is White's turn"));
6526             return FALSE;
6527         }
6528         if (white_piece && !WhiteOnMove(currentMove)) {
6529             DisplayMoveError(_("It is Black's turn"));
6530             return FALSE;
6531         }
6532         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6533             /* Editing correspondence game history */
6534             /* Could disallow this or prompt for confirmation */
6535             cmailOldMove = -1;
6536         }
6537         break;
6538
6539       case BeginningOfGame:
6540         if (appData.icsActive) return FALSE;
6541         if (!appData.noChessProgram) {
6542             if (!white_piece) {
6543                 DisplayMoveError(_("You are playing White"));
6544                 return FALSE;
6545             }
6546         }
6547         break;
6548
6549       case Training:
6550         if (!white_piece && WhiteOnMove(currentMove)) {
6551             DisplayMoveError(_("It is White's turn"));
6552             return FALSE;
6553         }
6554         if (white_piece && !WhiteOnMove(currentMove)) {
6555             DisplayMoveError(_("It is Black's turn"));
6556             return FALSE;
6557         }
6558         break;
6559
6560       default:
6561       case IcsExamining:
6562         break;
6563     }
6564     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6565         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6566         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6567         && gameMode != AnalyzeFile && gameMode != Training) {
6568         DisplayMoveError(_("Displayed position is not current"));
6569         return FALSE;
6570     }
6571     return TRUE;
6572 }
6573
6574 Boolean
6575 OnlyMove (int *x, int *y, Boolean captures)
6576 {
6577     DisambiguateClosure cl;
6578     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6579     switch(gameMode) {
6580       case MachinePlaysBlack:
6581       case IcsPlayingWhite:
6582       case BeginningOfGame:
6583         if(!WhiteOnMove(currentMove)) return FALSE;
6584         break;
6585       case MachinePlaysWhite:
6586       case IcsPlayingBlack:
6587         if(WhiteOnMove(currentMove)) return FALSE;
6588         break;
6589       case EditGame:
6590         break;
6591       default:
6592         return FALSE;
6593     }
6594     cl.pieceIn = EmptySquare;
6595     cl.rfIn = *y;
6596     cl.ffIn = *x;
6597     cl.rtIn = -1;
6598     cl.ftIn = -1;
6599     cl.promoCharIn = NULLCHAR;
6600     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6601     if( cl.kind == NormalMove ||
6602         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6603         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6604         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6605       fromX = cl.ff;
6606       fromY = cl.rf;
6607       *x = cl.ft;
6608       *y = cl.rt;
6609       return TRUE;
6610     }
6611     if(cl.kind != ImpossibleMove) return FALSE;
6612     cl.pieceIn = EmptySquare;
6613     cl.rfIn = -1;
6614     cl.ffIn = -1;
6615     cl.rtIn = *y;
6616     cl.ftIn = *x;
6617     cl.promoCharIn = NULLCHAR;
6618     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6619     if( cl.kind == NormalMove ||
6620         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6621         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6622         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6623       fromX = cl.ff;
6624       fromY = cl.rf;
6625       *x = cl.ft;
6626       *y = cl.rt;
6627       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6628       return TRUE;
6629     }
6630     return FALSE;
6631 }
6632
6633 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6634 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6635 int lastLoadGameUseList = FALSE;
6636 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6637 ChessMove lastLoadGameStart = EndOfFile;
6638 int doubleClick;
6639
6640 void
6641 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6642 {
6643     ChessMove moveType;
6644     ChessSquare pup;
6645     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6646
6647     /* Check if the user is playing in turn.  This is complicated because we
6648        let the user "pick up" a piece before it is his turn.  So the piece he
6649        tried to pick up may have been captured by the time he puts it down!
6650        Therefore we use the color the user is supposed to be playing in this
6651        test, not the color of the piece that is currently on the starting
6652        square---except in EditGame mode, where the user is playing both
6653        sides; fortunately there the capture race can't happen.  (It can
6654        now happen in IcsExamining mode, but that's just too bad.  The user
6655        will get a somewhat confusing message in that case.)
6656        */
6657
6658     switch (gameMode) {
6659       case AnalyzeFile:
6660       case TwoMachinesPlay:
6661       case EndOfGame:
6662       case IcsObserving:
6663       case IcsIdle:
6664         /* We switched into a game mode where moves are not accepted,
6665            perhaps while the mouse button was down. */
6666         return;
6667
6668       case MachinePlaysWhite:
6669         /* User is moving for Black */
6670         if (WhiteOnMove(currentMove)) {
6671             DisplayMoveError(_("It is White's turn"));
6672             return;
6673         }
6674         break;
6675
6676       case MachinePlaysBlack:
6677         /* User is moving for White */
6678         if (!WhiteOnMove(currentMove)) {
6679             DisplayMoveError(_("It is Black's turn"));
6680             return;
6681         }
6682         break;
6683
6684       case PlayFromGameFile:
6685             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6686       case EditGame:
6687       case IcsExamining:
6688       case BeginningOfGame:
6689       case AnalyzeMode:
6690       case Training:
6691         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6692         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6693             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6694             /* User is moving for Black */
6695             if (WhiteOnMove(currentMove)) {
6696                 DisplayMoveError(_("It is White's turn"));
6697                 return;
6698             }
6699         } else {
6700             /* User is moving for White */
6701             if (!WhiteOnMove(currentMove)) {
6702                 DisplayMoveError(_("It is Black's turn"));
6703                 return;
6704             }
6705         }
6706         break;
6707
6708       case IcsPlayingBlack:
6709         /* User is moving for Black */
6710         if (WhiteOnMove(currentMove)) {
6711             if (!appData.premove) {
6712                 DisplayMoveError(_("It is White's turn"));
6713             } else if (toX >= 0 && toY >= 0) {
6714                 premoveToX = toX;
6715                 premoveToY = toY;
6716                 premoveFromX = fromX;
6717                 premoveFromY = fromY;
6718                 premovePromoChar = promoChar;
6719                 gotPremove = 1;
6720                 if (appData.debugMode)
6721                     fprintf(debugFP, "Got premove: fromX %d,"
6722                             "fromY %d, toX %d, toY %d\n",
6723                             fromX, fromY, toX, toY);
6724             }
6725             return;
6726         }
6727         break;
6728
6729       case IcsPlayingWhite:
6730         /* User is moving for White */
6731         if (!WhiteOnMove(currentMove)) {
6732             if (!appData.premove) {
6733                 DisplayMoveError(_("It is Black's turn"));
6734             } else if (toX >= 0 && toY >= 0) {
6735                 premoveToX = toX;
6736                 premoveToY = toY;
6737                 premoveFromX = fromX;
6738                 premoveFromY = fromY;
6739                 premovePromoChar = promoChar;
6740                 gotPremove = 1;
6741                 if (appData.debugMode)
6742                     fprintf(debugFP, "Got premove: fromX %d,"
6743                             "fromY %d, toX %d, toY %d\n",
6744                             fromX, fromY, toX, toY);
6745             }
6746             return;
6747         }
6748         break;
6749
6750       default:
6751         break;
6752
6753       case EditPosition:
6754         /* EditPosition, empty square, or different color piece;
6755            click-click move is possible */
6756         if (toX == -2 || toY == -2) {
6757             boards[0][fromY][fromX] = EmptySquare;
6758             DrawPosition(FALSE, boards[currentMove]);
6759             return;
6760         } else if (toX >= 0 && toY >= 0) {
6761             boards[0][toY][toX] = boards[0][fromY][fromX];
6762             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6763                 if(boards[0][fromY][0] != EmptySquare) {
6764                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6765                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6766                 }
6767             } else
6768             if(fromX == BOARD_RGHT+1) {
6769                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6770                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6771                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6772                 }
6773             } else
6774             boards[0][fromY][fromX] = gatingPiece;
6775             DrawPosition(FALSE, boards[currentMove]);
6776             return;
6777         }
6778         return;
6779     }
6780
6781     if(toX < 0 || toY < 0) return;
6782     pup = boards[currentMove][toY][toX];
6783
6784     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6785     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6786          if( pup != EmptySquare ) return;
6787          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6788            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6789                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6790            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6791            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6792            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6793            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6794          fromY = DROP_RANK;
6795     }
6796
6797     /* [HGM] always test for legality, to get promotion info */
6798     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6799                                          fromY, fromX, toY, toX, promoChar);
6800
6801     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6802
6803     /* [HGM] but possibly ignore an IllegalMove result */
6804     if (appData.testLegality) {
6805         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6806             DisplayMoveError(_("Illegal move"));
6807             return;
6808         }
6809     }
6810
6811     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6812         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6813              ClearPremoveHighlights(); // was included
6814         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6815         return;
6816     }
6817
6818     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6819 }
6820
6821 /* Common tail of UserMoveEvent and DropMenuEvent */
6822 int
6823 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6824 {
6825     char *bookHit = 0;
6826
6827     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6828         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6829         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6830         if(WhiteOnMove(currentMove)) {
6831             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6832         } else {
6833             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6834         }
6835     }
6836
6837     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6838        move type in caller when we know the move is a legal promotion */
6839     if(moveType == NormalMove && promoChar)
6840         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6841
6842     /* [HGM] <popupFix> The following if has been moved here from
6843        UserMoveEvent(). Because it seemed to belong here (why not allow
6844        piece drops in training games?), and because it can only be
6845        performed after it is known to what we promote. */
6846     if (gameMode == Training) {
6847       /* compare the move played on the board to the next move in the
6848        * game. If they match, display the move and the opponent's response.
6849        * If they don't match, display an error message.
6850        */
6851       int saveAnimate;
6852       Board testBoard;
6853       CopyBoard(testBoard, boards[currentMove]);
6854       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6855
6856       if (CompareBoards(testBoard, boards[currentMove+1])) {
6857         ForwardInner(currentMove+1);
6858
6859         /* Autoplay the opponent's response.
6860          * if appData.animate was TRUE when Training mode was entered,
6861          * the response will be animated.
6862          */
6863         saveAnimate = appData.animate;
6864         appData.animate = animateTraining;
6865         ForwardInner(currentMove+1);
6866         appData.animate = saveAnimate;
6867
6868         /* check for the end of the game */
6869         if (currentMove >= forwardMostMove) {
6870           gameMode = PlayFromGameFile;
6871           ModeHighlight();
6872           SetTrainingModeOff();
6873           DisplayInformation(_("End of game"));
6874         }
6875       } else {
6876         DisplayError(_("Incorrect move"), 0);
6877       }
6878       return 1;
6879     }
6880
6881   /* Ok, now we know that the move is good, so we can kill
6882      the previous line in Analysis Mode */
6883   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6884                                 && currentMove < forwardMostMove) {
6885     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6886     else forwardMostMove = currentMove;
6887   }
6888
6889   ClearMap();
6890
6891   /* If we need the chess program but it's dead, restart it */
6892   ResurrectChessProgram();
6893
6894   /* A user move restarts a paused game*/
6895   if (pausing)
6896     PauseEvent();
6897
6898   thinkOutput[0] = NULLCHAR;
6899
6900   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6901
6902   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6903     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6904     return 1;
6905   }
6906
6907   if (gameMode == BeginningOfGame) {
6908     if (appData.noChessProgram) {
6909       gameMode = EditGame;
6910       SetGameInfo();
6911     } else {
6912       char buf[MSG_SIZ];
6913       gameMode = MachinePlaysBlack;
6914       StartClocks();
6915       SetGameInfo();
6916       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6917       DisplayTitle(buf);
6918       if (first.sendName) {
6919         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6920         SendToProgram(buf, &first);
6921       }
6922       StartClocks();
6923     }
6924     ModeHighlight();
6925   }
6926
6927   /* Relay move to ICS or chess engine */
6928   if (appData.icsActive) {
6929     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6930         gameMode == IcsExamining) {
6931       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6932         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6933         SendToICS("draw ");
6934         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6935       }
6936       // also send plain move, in case ICS does not understand atomic claims
6937       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6938       ics_user_moved = 1;
6939     }
6940   } else {
6941     if (first.sendTime && (gameMode == BeginningOfGame ||
6942                            gameMode == MachinePlaysWhite ||
6943                            gameMode == MachinePlaysBlack)) {
6944       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6945     }
6946     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6947          // [HGM] book: if program might be playing, let it use book
6948         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6949         first.maybeThinking = TRUE;
6950     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6951         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6952         SendBoard(&first, currentMove+1);
6953         if(second.analyzing) {
6954             if(!second.useSetboard) SendToProgram("undo\n", &second);
6955             SendBoard(&second, currentMove+1);
6956         }
6957     } else {
6958         SendMoveToProgram(forwardMostMove-1, &first);
6959         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6960     }
6961     if (currentMove == cmailOldMove + 1) {
6962       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6963     }
6964   }
6965
6966   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6967
6968   switch (gameMode) {
6969   case EditGame:
6970     if(appData.testLegality)
6971     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6972     case MT_NONE:
6973     case MT_CHECK:
6974       break;
6975     case MT_CHECKMATE:
6976     case MT_STAINMATE:
6977       if (WhiteOnMove(currentMove)) {
6978         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6979       } else {
6980         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6981       }
6982       break;
6983     case MT_STALEMATE:
6984       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6985       break;
6986     }
6987     break;
6988
6989   case MachinePlaysBlack:
6990   case MachinePlaysWhite:
6991     /* disable certain menu options while machine is thinking */
6992     SetMachineThinkingEnables();
6993     break;
6994
6995   default:
6996     break;
6997   }
6998
6999   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7000   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7001
7002   if(bookHit) { // [HGM] book: simulate book reply
7003         static char bookMove[MSG_SIZ]; // a bit generous?
7004
7005         programStats.nodes = programStats.depth = programStats.time =
7006         programStats.score = programStats.got_only_move = 0;
7007         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7008
7009         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7010         strcat(bookMove, bookHit);
7011         HandleMachineMove(bookMove, &first);
7012   }
7013   return 1;
7014 }
7015
7016 void
7017 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7018 {
7019     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7020     Markers *m = (Markers *) closure;
7021     if(rf == fromY && ff == fromX)
7022         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7023                          || kind == WhiteCapturesEnPassant
7024                          || kind == BlackCapturesEnPassant);
7025     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7026 }
7027
7028 void
7029 MarkTargetSquares (int clear)
7030 {
7031   int x, y;
7032   if(clear) // no reason to ever suppress clearing
7033     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7034   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7035      !appData.testLegality || gameMode == EditPosition) return;
7036   if(!clear) {
7037     int capt = 0;
7038     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7039     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7040       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7041       if(capt)
7042       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7043     }
7044   }
7045   DrawPosition(FALSE, NULL);
7046 }
7047
7048 int
7049 Explode (Board board, int fromX, int fromY, int toX, int toY)
7050 {
7051     if(gameInfo.variant == VariantAtomic &&
7052        (board[toY][toX] != EmptySquare ||                     // capture?
7053         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7054                          board[fromY][fromX] == BlackPawn   )
7055       )) {
7056         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7057         return TRUE;
7058     }
7059     return FALSE;
7060 }
7061
7062 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7063
7064 int
7065 CanPromote (ChessSquare piece, int y)
7066 {
7067         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7068         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7069         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7070            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7071            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7072          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7073         return (piece == BlackPawn && y == 1 ||
7074                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7075                 piece == BlackLance && y == 1 ||
7076                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7077 }
7078
7079 void
7080 LeftClick (ClickType clickType, int xPix, int yPix)
7081 {
7082     int x, y;
7083     Boolean saveAnimate;
7084     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7085     char promoChoice = NULLCHAR;
7086     ChessSquare piece;
7087     static TimeMark lastClickTime, prevClickTime;
7088
7089     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7090
7091     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7092
7093     if (clickType == Press) ErrorPopDown();
7094
7095     x = EventToSquare(xPix, BOARD_WIDTH);
7096     y = EventToSquare(yPix, BOARD_HEIGHT);
7097     if (!flipView && y >= 0) {
7098         y = BOARD_HEIGHT - 1 - y;
7099     }
7100     if (flipView && x >= 0) {
7101         x = BOARD_WIDTH - 1 - x;
7102     }
7103
7104     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7105         defaultPromoChoice = promoSweep;
7106         promoSweep = EmptySquare;   // terminate sweep
7107         promoDefaultAltered = TRUE;
7108         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7109     }
7110
7111     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7112         if(clickType == Release) return; // ignore upclick of click-click destination
7113         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7114         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7115         if(gameInfo.holdingsWidth &&
7116                 (WhiteOnMove(currentMove)
7117                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7118                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7119             // click in right holdings, for determining promotion piece
7120             ChessSquare p = boards[currentMove][y][x];
7121             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7122             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7123             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7124                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7125                 fromX = fromY = -1;
7126                 return;
7127             }
7128         }
7129         DrawPosition(FALSE, boards[currentMove]);
7130         return;
7131     }
7132
7133     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7134     if(clickType == Press
7135             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7136               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7137               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7138         return;
7139
7140     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7141         // could be static click on premove from-square: abort premove
7142         gotPremove = 0;
7143         ClearPremoveHighlights();
7144     }
7145
7146     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7147         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7148
7149     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7150         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7151                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7152         defaultPromoChoice = DefaultPromoChoice(side);
7153     }
7154
7155     autoQueen = appData.alwaysPromoteToQueen;
7156
7157     if (fromX == -1) {
7158       int originalY = y;
7159       gatingPiece = EmptySquare;
7160       if (clickType != Press) {
7161         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7162             DragPieceEnd(xPix, yPix); dragging = 0;
7163             DrawPosition(FALSE, NULL);
7164         }
7165         return;
7166       }
7167       doubleClick = FALSE;
7168       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7169         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7170       }
7171       fromX = x; fromY = y; toX = toY = -1;
7172       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7173          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7174          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7175             /* First square */
7176             if (OKToStartUserMove(fromX, fromY)) {
7177                 second = 0;
7178                 MarkTargetSquares(0);
7179                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7180                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7181                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7182                     promoSweep = defaultPromoChoice;
7183                     selectFlag = 0; lastX = xPix; lastY = yPix;
7184                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7185                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7186                 }
7187                 if (appData.highlightDragging) {
7188                     SetHighlights(fromX, fromY, -1, -1);
7189                 } else {
7190                     ClearHighlights();
7191                 }
7192             } else fromX = fromY = -1;
7193             return;
7194         }
7195     }
7196
7197     /* fromX != -1 */
7198     if (clickType == Press && gameMode != EditPosition) {
7199         ChessSquare fromP;
7200         ChessSquare toP;
7201         int frc;
7202
7203         // ignore off-board to clicks
7204         if(y < 0 || x < 0) return;
7205
7206         /* Check if clicking again on the same color piece */
7207         fromP = boards[currentMove][fromY][fromX];
7208         toP = boards[currentMove][y][x];
7209         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7210         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7211              WhitePawn <= toP && toP <= WhiteKing &&
7212              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7213              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7214             (BlackPawn <= fromP && fromP <= BlackKing &&
7215              BlackPawn <= toP && toP <= BlackKing &&
7216              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7217              !(fromP == BlackKing && toP == BlackRook && frc))) {
7218             /* Clicked again on same color piece -- changed his mind */
7219             second = (x == fromX && y == fromY);
7220             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7221                 second = FALSE; // first double-click rather than scond click
7222                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7223             }
7224             promoDefaultAltered = FALSE;
7225             MarkTargetSquares(1);
7226            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7227             if (appData.highlightDragging) {
7228                 SetHighlights(x, y, -1, -1);
7229             } else {
7230                 ClearHighlights();
7231             }
7232             if (OKToStartUserMove(x, y)) {
7233                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7234                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7235                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7236                  gatingPiece = boards[currentMove][fromY][fromX];
7237                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7238                 fromX = x;
7239                 fromY = y; dragging = 1;
7240                 MarkTargetSquares(0);
7241                 DragPieceBegin(xPix, yPix, FALSE);
7242                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7243                     promoSweep = defaultPromoChoice;
7244                     selectFlag = 0; lastX = xPix; lastY = yPix;
7245                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7246                 }
7247             }
7248            }
7249            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7250            second = FALSE;
7251         }
7252         // ignore clicks on holdings
7253         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7254     }
7255
7256     if (clickType == Release && x == fromX && y == fromY) {
7257         DragPieceEnd(xPix, yPix); dragging = 0;
7258         if(clearFlag) {
7259             // a deferred attempt to click-click move an empty square on top of a piece
7260             boards[currentMove][y][x] = EmptySquare;
7261             ClearHighlights();
7262             DrawPosition(FALSE, boards[currentMove]);
7263             fromX = fromY = -1; clearFlag = 0;
7264             return;
7265         }
7266         if (appData.animateDragging) {
7267             /* Undo animation damage if any */
7268             DrawPosition(FALSE, NULL);
7269         }
7270         if (second || sweepSelecting) {
7271             /* Second up/down in same square; just abort move */
7272             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7273             second = sweepSelecting = 0;
7274             fromX = fromY = -1;
7275             gatingPiece = EmptySquare;
7276             ClearHighlights();
7277             gotPremove = 0;
7278             ClearPremoveHighlights();
7279         } else {
7280             /* First upclick in same square; start click-click mode */
7281             SetHighlights(x, y, -1, -1);
7282         }
7283         return;
7284     }
7285
7286     clearFlag = 0;
7287
7288     /* we now have a different from- and (possibly off-board) to-square */
7289     /* Completed move */
7290     if(!sweepSelecting) {
7291         toX = x;
7292         toY = y;
7293     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7294
7295     saveAnimate = appData.animate;
7296     if (clickType == Press) {
7297         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7298             // must be Edit Position mode with empty-square selected
7299             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7300             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7301             return;
7302         }
7303         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7304           if(appData.sweepSelect) {
7305             ChessSquare piece = boards[currentMove][fromY][fromX];
7306             promoSweep = defaultPromoChoice;
7307             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7308             selectFlag = 0; lastX = xPix; lastY = yPix;
7309             Sweep(0); // Pawn that is going to promote: preview promotion piece
7310             sweepSelecting = 1;
7311             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7312             MarkTargetSquares(1);
7313           }
7314           return; // promo popup appears on up-click
7315         }
7316         /* Finish clickclick move */
7317         if (appData.animate || appData.highlightLastMove) {
7318             SetHighlights(fromX, fromY, toX, toY);
7319         } else {
7320             ClearHighlights();
7321         }
7322     } else {
7323 #if 0
7324 // [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
7325         /* Finish drag move */
7326         if (appData.highlightLastMove) {
7327             SetHighlights(fromX, fromY, toX, toY);
7328         } else {
7329             ClearHighlights();
7330         }
7331 #endif
7332         DragPieceEnd(xPix, yPix); dragging = 0;
7333         /* Don't animate move and drag both */
7334         appData.animate = FALSE;
7335     }
7336
7337     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7338     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7339         ChessSquare piece = boards[currentMove][fromY][fromX];
7340         if(gameMode == EditPosition && piece != EmptySquare &&
7341            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7342             int n;
7343
7344             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7345                 n = PieceToNumber(piece - (int)BlackPawn);
7346                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7347                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7348                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7349             } else
7350             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7351                 n = PieceToNumber(piece);
7352                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7353                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7354                 boards[currentMove][n][BOARD_WIDTH-2]++;
7355             }
7356             boards[currentMove][fromY][fromX] = EmptySquare;
7357         }
7358         ClearHighlights();
7359         fromX = fromY = -1;
7360         MarkTargetSquares(1);
7361         DrawPosition(TRUE, boards[currentMove]);
7362         return;
7363     }
7364
7365     // off-board moves should not be highlighted
7366     if(x < 0 || y < 0) ClearHighlights();
7367
7368     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7369
7370     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7371         SetHighlights(fromX, fromY, toX, toY);
7372         MarkTargetSquares(1);
7373         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7374             // [HGM] super: promotion to captured piece selected from holdings
7375             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7376             promotionChoice = TRUE;
7377             // kludge follows to temporarily execute move on display, without promoting yet
7378             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7379             boards[currentMove][toY][toX] = p;
7380             DrawPosition(FALSE, boards[currentMove]);
7381             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7382             boards[currentMove][toY][toX] = q;
7383             DisplayMessage("Click in holdings to choose piece", "");
7384             return;
7385         }
7386         PromotionPopUp();
7387     } else {
7388         int oldMove = currentMove;
7389         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7390         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7391         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7392         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7393            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7394             DrawPosition(TRUE, boards[currentMove]);
7395         MarkTargetSquares(1);
7396         fromX = fromY = -1;
7397     }
7398     appData.animate = saveAnimate;
7399     if (appData.animate || appData.animateDragging) {
7400         /* Undo animation damage if needed */
7401         DrawPosition(FALSE, NULL);
7402     }
7403 }
7404
7405 int
7406 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7407 {   // front-end-free part taken out of PieceMenuPopup
7408     int whichMenu; int xSqr, ySqr;
7409
7410     if(seekGraphUp) { // [HGM] seekgraph
7411         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7412         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7413         return -2;
7414     }
7415
7416     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7417          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7418         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7419         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7420         if(action == Press)   {
7421             originalFlip = flipView;
7422             flipView = !flipView; // temporarily flip board to see game from partners perspective
7423             DrawPosition(TRUE, partnerBoard);
7424             DisplayMessage(partnerStatus, "");
7425             partnerUp = TRUE;
7426         } else if(action == Release) {
7427             flipView = originalFlip;
7428             DrawPosition(TRUE, boards[currentMove]);
7429             partnerUp = FALSE;
7430         }
7431         return -2;
7432     }
7433
7434     xSqr = EventToSquare(x, BOARD_WIDTH);
7435     ySqr = EventToSquare(y, BOARD_HEIGHT);
7436     if (action == Release) {
7437         if(pieceSweep != EmptySquare) {
7438             EditPositionMenuEvent(pieceSweep, toX, toY);
7439             pieceSweep = EmptySquare;
7440         } else UnLoadPV(); // [HGM] pv
7441     }
7442     if (action != Press) return -2; // return code to be ignored
7443     switch (gameMode) {
7444       case IcsExamining:
7445         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7446       case EditPosition:
7447         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7448         if (xSqr < 0 || ySqr < 0) return -1;
7449         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7450         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7451         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7452         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7453         NextPiece(0);
7454         return 2; // grab
7455       case IcsObserving:
7456         if(!appData.icsEngineAnalyze) return -1;
7457       case IcsPlayingWhite:
7458       case IcsPlayingBlack:
7459         if(!appData.zippyPlay) goto noZip;
7460       case AnalyzeMode:
7461       case AnalyzeFile:
7462       case MachinePlaysWhite:
7463       case MachinePlaysBlack:
7464       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7465         if (!appData.dropMenu) {
7466           LoadPV(x, y);
7467           return 2; // flag front-end to grab mouse events
7468         }
7469         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7470            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7471       case EditGame:
7472       noZip:
7473         if (xSqr < 0 || ySqr < 0) return -1;
7474         if (!appData.dropMenu || appData.testLegality &&
7475             gameInfo.variant != VariantBughouse &&
7476             gameInfo.variant != VariantCrazyhouse) return -1;
7477         whichMenu = 1; // drop menu
7478         break;
7479       default:
7480         return -1;
7481     }
7482
7483     if (((*fromX = xSqr) < 0) ||
7484         ((*fromY = ySqr) < 0)) {
7485         *fromX = *fromY = -1;
7486         return -1;
7487     }
7488     if (flipView)
7489       *fromX = BOARD_WIDTH - 1 - *fromX;
7490     else
7491       *fromY = BOARD_HEIGHT - 1 - *fromY;
7492
7493     return whichMenu;
7494 }
7495
7496 void
7497 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7498 {
7499 //    char * hint = lastHint;
7500     FrontEndProgramStats stats;
7501
7502     stats.which = cps == &first ? 0 : 1;
7503     stats.depth = cpstats->depth;
7504     stats.nodes = cpstats->nodes;
7505     stats.score = cpstats->score;
7506     stats.time = cpstats->time;
7507     stats.pv = cpstats->movelist;
7508     stats.hint = lastHint;
7509     stats.an_move_index = 0;
7510     stats.an_move_count = 0;
7511
7512     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7513         stats.hint = cpstats->move_name;
7514         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7515         stats.an_move_count = cpstats->nr_moves;
7516     }
7517
7518     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
7519
7520     SetProgramStats( &stats );
7521 }
7522
7523 void
7524 ClearEngineOutputPane (int which)
7525 {
7526     static FrontEndProgramStats dummyStats;
7527     dummyStats.which = which;
7528     dummyStats.pv = "#";
7529     SetProgramStats( &dummyStats );
7530 }
7531
7532 #define MAXPLAYERS 500
7533
7534 char *
7535 TourneyStandings (int display)
7536 {
7537     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7538     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7539     char result, *p, *names[MAXPLAYERS];
7540
7541     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7542         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7543     names[0] = p = strdup(appData.participants);
7544     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7545
7546     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7547
7548     while(result = appData.results[nr]) {
7549         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7550         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7551         wScore = bScore = 0;
7552         switch(result) {
7553           case '+': wScore = 2; break;
7554           case '-': bScore = 2; break;
7555           case '=': wScore = bScore = 1; break;
7556           case ' ':
7557           case '*': return strdup("busy"); // tourney not finished
7558         }
7559         score[w] += wScore;
7560         score[b] += bScore;
7561         games[w]++;
7562         games[b]++;
7563         nr++;
7564     }
7565     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7566     for(w=0; w<nPlayers; w++) {
7567         bScore = -1;
7568         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7569         ranking[w] = b; points[w] = bScore; score[b] = -2;
7570     }
7571     p = malloc(nPlayers*34+1);
7572     for(w=0; w<nPlayers && w<display; w++)
7573         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7574     free(names[0]);
7575     return p;
7576 }
7577
7578 void
7579 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7580 {       // count all piece types
7581         int p, f, r;
7582         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7583         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7584         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7585                 p = board[r][f];
7586                 pCnt[p]++;
7587                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7588                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7589                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7590                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7591                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7592                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7593         }
7594 }
7595
7596 int
7597 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7598 {
7599         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7600         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7601
7602         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7603         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7604         if(myPawns == 2 && nMine == 3) // KPP
7605             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7606         if(myPawns == 1 && nMine == 2) // KP
7607             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7608         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7609             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7610         if(myPawns) return FALSE;
7611         if(pCnt[WhiteRook+side])
7612             return pCnt[BlackRook-side] ||
7613                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7614                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7615                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7616         if(pCnt[WhiteCannon+side]) {
7617             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7618             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7619         }
7620         if(pCnt[WhiteKnight+side])
7621             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7622         return FALSE;
7623 }
7624
7625 int
7626 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7627 {
7628         VariantClass v = gameInfo.variant;
7629
7630         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7631         if(v == VariantShatranj) return TRUE; // always winnable through baring
7632         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7633         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7634
7635         if(v == VariantXiangqi) {
7636                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7637
7638                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7639                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7640                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7641                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7642                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7643                 if(stale) // we have at least one last-rank P plus perhaps C
7644                     return majors // KPKX
7645                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7646                 else // KCA*E*
7647                     return pCnt[WhiteFerz+side] // KCAK
7648                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7649                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7650                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7651
7652         } else if(v == VariantKnightmate) {
7653                 if(nMine == 1) return FALSE;
7654                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7655         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7656                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7657
7658                 if(nMine == 1) return FALSE; // bare King
7659                 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
7660                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7661                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7662                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7663                 if(pCnt[WhiteKnight+side])
7664                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7665                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7666                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7667                 if(nBishops)
7668                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7669                 if(pCnt[WhiteAlfil+side])
7670                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7671                 if(pCnt[WhiteWazir+side])
7672                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7673         }
7674
7675         return TRUE;
7676 }
7677
7678 int
7679 CompareWithRights (Board b1, Board b2)
7680 {
7681     int rights = 0;
7682     if(!CompareBoards(b1, b2)) return FALSE;
7683     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7684     /* compare castling rights */
7685     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7686            rights++; /* King lost rights, while rook still had them */
7687     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7688         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7689            rights++; /* but at least one rook lost them */
7690     }
7691     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7692            rights++;
7693     if( b1[CASTLING][5] != NoRights ) {
7694         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7695            rights++;
7696     }
7697     return rights == 0;
7698 }
7699
7700 int
7701 Adjudicate (ChessProgramState *cps)
7702 {       // [HGM] some adjudications useful with buggy engines
7703         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7704         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7705         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7706         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7707         int k, drop, count = 0; static int bare = 1;
7708         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7709         Boolean canAdjudicate = !appData.icsActive;
7710
7711         // most tests only when we understand the game, i.e. legality-checking on
7712             if( appData.testLegality )
7713             {   /* [HGM] Some more adjudications for obstinate engines */
7714                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7715                 static int moveCount = 6;
7716                 ChessMove result;
7717                 char *reason = NULL;
7718
7719                 /* Count what is on board. */
7720                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7721
7722                 /* Some material-based adjudications that have to be made before stalemate test */
7723                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7724                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7725                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7726                      if(canAdjudicate && appData.checkMates) {
7727                          if(engineOpponent)
7728                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7729                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7730                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7731                          return 1;
7732                      }
7733                 }
7734
7735                 /* Bare King in Shatranj (loses) or Losers (wins) */
7736                 if( nrW == 1 || nrB == 1) {
7737                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7738                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7739                      if(canAdjudicate && appData.checkMates) {
7740                          if(engineOpponent)
7741                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7742                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7743                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7744                          return 1;
7745                      }
7746                   } else
7747                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7748                   {    /* bare King */
7749                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7750                         if(canAdjudicate && appData.checkMates) {
7751                             /* but only adjudicate if adjudication enabled */
7752                             if(engineOpponent)
7753                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7754                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7755                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7756                             return 1;
7757                         }
7758                   }
7759                 } else bare = 1;
7760
7761
7762             // don't wait for engine to announce game end if we can judge ourselves
7763             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7764               case MT_CHECK:
7765                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7766                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7767                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7768                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7769                             checkCnt++;
7770                         if(checkCnt >= 2) {
7771                             reason = "Xboard adjudication: 3rd check";
7772                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7773                             break;
7774                         }
7775                     }
7776                 }
7777               case MT_NONE:
7778               default:
7779                 break;
7780               case MT_STALEMATE:
7781               case MT_STAINMATE:
7782                 reason = "Xboard adjudication: Stalemate";
7783                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7784                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7785                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7786                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7787                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7788                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7789                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7790                                                                         EP_CHECKMATE : EP_WINS);
7791                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7792                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7793                 }
7794                 break;
7795               case MT_CHECKMATE:
7796                 reason = "Xboard adjudication: Checkmate";
7797                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7798                 if(gameInfo.variant == VariantShogi) {
7799                     if(forwardMostMove > backwardMostMove
7800                        && moveList[forwardMostMove-1][1] == '@'
7801                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7802                         reason = "XBoard adjudication: pawn-drop mate";
7803                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7804                     }
7805                 }
7806                 break;
7807             }
7808
7809                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7810                     case EP_STALEMATE:
7811                         result = GameIsDrawn; break;
7812                     case EP_CHECKMATE:
7813                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7814                     case EP_WINS:
7815                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7816                     default:
7817                         result = EndOfFile;
7818                 }
7819                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7820                     if(engineOpponent)
7821                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7822                     GameEnds( result, reason, GE_XBOARD );
7823                     return 1;
7824                 }
7825
7826                 /* Next absolutely insufficient mating material. */
7827                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7828                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7829                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7830
7831                      /* always flag draws, for judging claims */
7832                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7833
7834                      if(canAdjudicate && appData.materialDraws) {
7835                          /* but only adjudicate them if adjudication enabled */
7836                          if(engineOpponent) {
7837                            SendToProgram("force\n", engineOpponent); // suppress reply
7838                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7839                          }
7840                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7841                          return 1;
7842                      }
7843                 }
7844
7845                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7846                 if(gameInfo.variant == VariantXiangqi ?
7847                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7848                  : nrW + nrB == 4 &&
7849                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7850                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7851                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7852                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7853                    ) ) {
7854                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7855                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7856                           if(engineOpponent) {
7857                             SendToProgram("force\n", engineOpponent); // suppress reply
7858                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7859                           }
7860                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7861                           return 1;
7862                      }
7863                 } else moveCount = 6;
7864             }
7865
7866         // Repetition draws and 50-move rule can be applied independently of legality testing
7867
7868                 /* Check for rep-draws */
7869                 count = 0;
7870                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7871                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7872                 for(k = forwardMostMove-2;
7873                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7874                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7875                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7876                     k-=2)
7877                 {   int rights=0;
7878                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7879                         /* compare castling rights */
7880                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7881                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7882                                 rights++; /* King lost rights, while rook still had them */
7883                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7884                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7885                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7886                                    rights++; /* but at least one rook lost them */
7887                         }
7888                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7889                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7890                                 rights++;
7891                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7892                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7893                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7894                                    rights++;
7895                         }
7896                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7897                             && appData.drawRepeats > 1) {
7898                              /* adjudicate after user-specified nr of repeats */
7899                              int result = GameIsDrawn;
7900                              char *details = "XBoard adjudication: repetition draw";
7901                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7902                                 // [HGM] xiangqi: check for forbidden perpetuals
7903                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7904                                 for(m=forwardMostMove; m>k; m-=2) {
7905                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7906                                         ourPerpetual = 0; // the current mover did not always check
7907                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7908                                         hisPerpetual = 0; // the opponent did not always check
7909                                 }
7910                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7911                                                                         ourPerpetual, hisPerpetual);
7912                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7913                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7914                                     details = "Xboard adjudication: perpetual checking";
7915                                 } else
7916                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7917                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7918                                 } else
7919                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7920                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7921                                         result = BlackWins;
7922                                         details = "Xboard adjudication: repetition";
7923                                     }
7924                                 } else // it must be XQ
7925                                 // Now check for perpetual chases
7926                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7927                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7928                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7929                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7930                                         static char resdet[MSG_SIZ];
7931                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7932                                         details = resdet;
7933                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7934                                     } else
7935                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7936                                         break; // Abort repetition-checking loop.
7937                                 }
7938                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7939                              }
7940                              if(engineOpponent) {
7941                                SendToProgram("force\n", engineOpponent); // suppress reply
7942                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7943                              }
7944                              GameEnds( result, details, GE_XBOARD );
7945                              return 1;
7946                         }
7947                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7948                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7949                     }
7950                 }
7951
7952                 /* Now we test for 50-move draws. Determine ply count */
7953                 count = forwardMostMove;
7954                 /* look for last irreversble move */
7955                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7956                     count--;
7957                 /* if we hit starting position, add initial plies */
7958                 if( count == backwardMostMove )
7959                     count -= initialRulePlies;
7960                 count = forwardMostMove - count;
7961                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7962                         // adjust reversible move counter for checks in Xiangqi
7963                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7964                         if(i < backwardMostMove) i = backwardMostMove;
7965                         while(i <= forwardMostMove) {
7966                                 lastCheck = inCheck; // check evasion does not count
7967                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7968                                 if(inCheck || lastCheck) count--; // check does not count
7969                                 i++;
7970                         }
7971                 }
7972                 if( count >= 100)
7973                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7974                          /* this is used to judge if draw claims are legal */
7975                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7976                          if(engineOpponent) {
7977                            SendToProgram("force\n", engineOpponent); // suppress reply
7978                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7979                          }
7980                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7981                          return 1;
7982                 }
7983
7984                 /* if draw offer is pending, treat it as a draw claim
7985                  * when draw condition present, to allow engines a way to
7986                  * claim draws before making their move to avoid a race
7987                  * condition occurring after their move
7988                  */
7989                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7990                          char *p = NULL;
7991                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7992                              p = "Draw claim: 50-move rule";
7993                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7994                              p = "Draw claim: 3-fold repetition";
7995                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7996                              p = "Draw claim: insufficient mating material";
7997                          if( p != NULL && canAdjudicate) {
7998                              if(engineOpponent) {
7999                                SendToProgram("force\n", engineOpponent); // suppress reply
8000                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8001                              }
8002                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8003                              return 1;
8004                          }
8005                 }
8006
8007                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8008                     if(engineOpponent) {
8009                       SendToProgram("force\n", engineOpponent); // suppress reply
8010                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8011                     }
8012                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8013                     return 1;
8014                 }
8015         return 0;
8016 }
8017
8018 char *
8019 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8020 {   // [HGM] book: this routine intercepts moves to simulate book replies
8021     char *bookHit = NULL;
8022
8023     //first determine if the incoming move brings opponent into his book
8024     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8025         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8026     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8027     if(bookHit != NULL && !cps->bookSuspend) {
8028         // make sure opponent is not going to reply after receiving move to book position
8029         SendToProgram("force\n", cps);
8030         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8031     }
8032     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8033     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8034     // now arrange restart after book miss
8035     if(bookHit) {
8036         // after a book hit we never send 'go', and the code after the call to this routine
8037         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8038         char buf[MSG_SIZ], *move = bookHit;
8039         if(cps->useSAN) {
8040             int fromX, fromY, toX, toY;
8041             char promoChar;
8042             ChessMove moveType;
8043             move = buf + 30;
8044             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8045                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8046                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8047                                     PosFlags(forwardMostMove),
8048                                     fromY, fromX, toY, toX, promoChar, move);
8049             } else {
8050                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8051                 bookHit = NULL;
8052             }
8053         }
8054         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8055         SendToProgram(buf, cps);
8056         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8057     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8058         SendToProgram("go\n", cps);
8059         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8060     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8061         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8062             SendToProgram("go\n", cps);
8063         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8064     }
8065     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8066 }
8067
8068 int
8069 LoadError (char *errmess, ChessProgramState *cps)
8070 {   // unloads engine and switches back to -ncp mode if it was first
8071     if(cps->initDone) return FALSE;
8072     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8073     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8074     cps->pr = NoProc;
8075     if(cps == &first) {
8076         appData.noChessProgram = TRUE;
8077         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8078         gameMode = BeginningOfGame; ModeHighlight();
8079         SetNCPMode();
8080     }
8081     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8082     DisplayMessage("", ""); // erase waiting message
8083     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8084     return TRUE;
8085 }
8086
8087 char *savedMessage;
8088 ChessProgramState *savedState;
8089 void
8090 DeferredBookMove (void)
8091 {
8092         if(savedState->lastPing != savedState->lastPong)
8093                     ScheduleDelayedEvent(DeferredBookMove, 10);
8094         else
8095         HandleMachineMove(savedMessage, savedState);
8096 }
8097
8098 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8099 static ChessProgramState *stalledEngine;
8100 static char stashedInputMove[MSG_SIZ];
8101
8102 void
8103 HandleMachineMove (char *message, ChessProgramState *cps)
8104 {
8105     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8106     char realname[MSG_SIZ];
8107     int fromX, fromY, toX, toY;
8108     ChessMove moveType;
8109     char promoChar;
8110     char *p, *pv=buf1;
8111     int machineWhite, oldError;
8112     char *bookHit;
8113
8114     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8115         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8116         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8117             DisplayError(_("Invalid pairing from pairing engine"), 0);
8118             return;
8119         }
8120         pairingReceived = 1;
8121         NextMatchGame();
8122         return; // Skim the pairing messages here.
8123     }
8124
8125     oldError = cps->userError; cps->userError = 0;
8126
8127 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8128     /*
8129      * Kludge to ignore BEL characters
8130      */
8131     while (*message == '\007') message++;
8132
8133     /*
8134      * [HGM] engine debug message: ignore lines starting with '#' character
8135      */
8136     if(cps->debug && *message == '#') return;
8137
8138     /*
8139      * Look for book output
8140      */
8141     if (cps == &first && bookRequested) {
8142         if (message[0] == '\t' || message[0] == ' ') {
8143             /* Part of the book output is here; append it */
8144             strcat(bookOutput, message);
8145             strcat(bookOutput, "  \n");
8146             return;
8147         } else if (bookOutput[0] != NULLCHAR) {
8148             /* All of book output has arrived; display it */
8149             char *p = bookOutput;
8150             while (*p != NULLCHAR) {
8151                 if (*p == '\t') *p = ' ';
8152                 p++;
8153             }
8154             DisplayInformation(bookOutput);
8155             bookRequested = FALSE;
8156             /* Fall through to parse the current output */
8157         }
8158     }
8159
8160     /*
8161      * Look for machine move.
8162      */
8163     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8164         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8165     {
8166         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8167             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8168             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8169             stalledEngine = cps;
8170             if(appData.ponderNextMove) { // bring opponent out of ponder
8171                 if(gameMode == TwoMachinesPlay) {
8172                     if(cps->other->pause)
8173                         PauseEngine(cps->other);
8174                     else
8175                         SendToProgram("easy\n", cps->other);
8176                 }
8177             }
8178             StopClocks();
8179             return;
8180         }
8181
8182         /* This method is only useful on engines that support ping */
8183         if (cps->lastPing != cps->lastPong) {
8184           if (gameMode == BeginningOfGame) {
8185             /* Extra move from before last new; ignore */
8186             if (appData.debugMode) {
8187                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8188             }
8189           } else {
8190             if (appData.debugMode) {
8191                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8192                         cps->which, gameMode);
8193             }
8194
8195             SendToProgram("undo\n", cps);
8196           }
8197           return;
8198         }
8199
8200         switch (gameMode) {
8201           case BeginningOfGame:
8202             /* Extra move from before last reset; ignore */
8203             if (appData.debugMode) {
8204                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8205             }
8206             return;
8207
8208           case EndOfGame:
8209           case IcsIdle:
8210           default:
8211             /* Extra move after we tried to stop.  The mode test is
8212                not a reliable way of detecting this problem, but it's
8213                the best we can do on engines that don't support ping.
8214             */
8215             if (appData.debugMode) {
8216                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8217                         cps->which, gameMode);
8218             }
8219             SendToProgram("undo\n", cps);
8220             return;
8221
8222           case MachinePlaysWhite:
8223           case IcsPlayingWhite:
8224             machineWhite = TRUE;
8225             break;
8226
8227           case MachinePlaysBlack:
8228           case IcsPlayingBlack:
8229             machineWhite = FALSE;
8230             break;
8231
8232           case TwoMachinesPlay:
8233             machineWhite = (cps->twoMachinesColor[0] == 'w');
8234             break;
8235         }
8236         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8237             if (appData.debugMode) {
8238                 fprintf(debugFP,
8239                         "Ignoring move out of turn by %s, gameMode %d"
8240                         ", forwardMost %d\n",
8241                         cps->which, gameMode, forwardMostMove);
8242             }
8243             return;
8244         }
8245
8246         if(cps->alphaRank) AlphaRank(machineMove, 4);
8247         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8248                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8249             /* Machine move could not be parsed; ignore it. */
8250           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8251                     machineMove, _(cps->which));
8252             DisplayMoveError(buf1);
8253             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8254                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8255             if (gameMode == TwoMachinesPlay) {
8256               GameEnds(machineWhite ? BlackWins : WhiteWins,
8257                        buf1, GE_XBOARD);
8258             }
8259             return;
8260         }
8261
8262         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8263         /* So we have to redo legality test with true e.p. status here,  */
8264         /* to make sure an illegal e.p. capture does not slip through,   */
8265         /* to cause a forfeit on a justified illegal-move complaint      */
8266         /* of the opponent.                                              */
8267         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8268            ChessMove moveType;
8269            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8270                              fromY, fromX, toY, toX, promoChar);
8271             if(moveType == IllegalMove) {
8272               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8273                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8274                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8275                            buf1, GE_XBOARD);
8276                 return;
8277            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8278            /* [HGM] Kludge to handle engines that send FRC-style castling
8279               when they shouldn't (like TSCP-Gothic) */
8280            switch(moveType) {
8281              case WhiteASideCastleFR:
8282              case BlackASideCastleFR:
8283                toX+=2;
8284                currentMoveString[2]++;
8285                break;
8286              case WhiteHSideCastleFR:
8287              case BlackHSideCastleFR:
8288                toX--;
8289                currentMoveString[2]--;
8290                break;
8291              default: ; // nothing to do, but suppresses warning of pedantic compilers
8292            }
8293         }
8294         hintRequested = FALSE;
8295         lastHint[0] = NULLCHAR;
8296         bookRequested = FALSE;
8297         /* Program may be pondering now */
8298         cps->maybeThinking = TRUE;
8299         if (cps->sendTime == 2) cps->sendTime = 1;
8300         if (cps->offeredDraw) cps->offeredDraw--;
8301
8302         /* [AS] Save move info*/
8303         pvInfoList[ forwardMostMove ].score = programStats.score;
8304         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8305         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8306
8307         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8308
8309         /* Test suites abort the 'game' after one move */
8310         if(*appData.finger) {
8311            static FILE *f;
8312            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8313            if(!f) f = fopen(appData.finger, "w");
8314            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8315            else { DisplayFatalError("Bad output file", errno, 0); return; }
8316            free(fen);
8317            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8318         }
8319
8320         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8321         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8322             int count = 0;
8323
8324             while( count < adjudicateLossPlies ) {
8325                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8326
8327                 if( count & 1 ) {
8328                     score = -score; /* Flip score for winning side */
8329                 }
8330
8331                 if( score > adjudicateLossThreshold ) {
8332                     break;
8333                 }
8334
8335                 count++;
8336             }
8337
8338             if( count >= adjudicateLossPlies ) {
8339                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8340
8341                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8342                     "Xboard adjudication",
8343                     GE_XBOARD );
8344
8345                 return;
8346             }
8347         }
8348
8349         if(Adjudicate(cps)) {
8350             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8351             return; // [HGM] adjudicate: for all automatic game ends
8352         }
8353
8354 #if ZIPPY
8355         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8356             first.initDone) {
8357           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8358                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8359                 SendToICS("draw ");
8360                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8361           }
8362           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8363           ics_user_moved = 1;
8364           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8365                 char buf[3*MSG_SIZ];
8366
8367                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8368                         programStats.score / 100.,
8369                         programStats.depth,
8370                         programStats.time / 100.,
8371                         (unsigned int)programStats.nodes,
8372                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8373                         programStats.movelist);
8374                 SendToICS(buf);
8375 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8376           }
8377         }
8378 #endif
8379
8380         /* [AS] Clear stats for next move */
8381         ClearProgramStats();
8382         thinkOutput[0] = NULLCHAR;
8383         hiddenThinkOutputState = 0;
8384
8385         bookHit = NULL;
8386         if (gameMode == TwoMachinesPlay) {
8387             /* [HGM] relaying draw offers moved to after reception of move */
8388             /* and interpreting offer as claim if it brings draw condition */
8389             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8390                 SendToProgram("draw\n", cps->other);
8391             }
8392             if (cps->other->sendTime) {
8393                 SendTimeRemaining(cps->other,
8394                                   cps->other->twoMachinesColor[0] == 'w');
8395             }
8396             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8397             if (firstMove && !bookHit) {
8398                 firstMove = FALSE;
8399                 if (cps->other->useColors) {
8400                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8401                 }
8402                 SendToProgram("go\n", cps->other);
8403             }
8404             cps->other->maybeThinking = TRUE;
8405         }
8406
8407         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8408
8409         if (!pausing && appData.ringBellAfterMoves) {
8410             RingBell();
8411         }
8412
8413         /*
8414          * Reenable menu items that were disabled while
8415          * machine was thinking
8416          */
8417         if (gameMode != TwoMachinesPlay)
8418             SetUserThinkingEnables();
8419
8420         // [HGM] book: after book hit opponent has received move and is now in force mode
8421         // force the book reply into it, and then fake that it outputted this move by jumping
8422         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8423         if(bookHit) {
8424                 static char bookMove[MSG_SIZ]; // a bit generous?
8425
8426                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8427                 strcat(bookMove, bookHit);
8428                 message = bookMove;
8429                 cps = cps->other;
8430                 programStats.nodes = programStats.depth = programStats.time =
8431                 programStats.score = programStats.got_only_move = 0;
8432                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8433
8434                 if(cps->lastPing != cps->lastPong) {
8435                     savedMessage = message; // args for deferred call
8436                     savedState = cps;
8437                     ScheduleDelayedEvent(DeferredBookMove, 10);
8438                     return;
8439                 }
8440                 goto FakeBookMove;
8441         }
8442
8443         return;
8444     }
8445
8446     /* Set special modes for chess engines.  Later something general
8447      *  could be added here; for now there is just one kludge feature,
8448      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8449      *  when "xboard" is given as an interactive command.
8450      */
8451     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8452         cps->useSigint = FALSE;
8453         cps->useSigterm = FALSE;
8454     }
8455     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8456       ParseFeatures(message+8, cps);
8457       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8458     }
8459
8460     if (!strncmp(message, "setup ", 6) && 
8461         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8462                                         ) { // [HGM] allow first engine to define opening position
8463       int dummy, s=6; char buf[MSG_SIZ];
8464       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8465       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8466       if(startedFromSetupPosition) return;
8467       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8468       ParseFEN(boards[0], &dummy, message+s);
8469       DrawPosition(TRUE, boards[0]);
8470       startedFromSetupPosition = TRUE;
8471       return;
8472     }
8473     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8474      * want this, I was asked to put it in, and obliged.
8475      */
8476     if (!strncmp(message, "setboard ", 9)) {
8477         Board initial_position;
8478
8479         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8480
8481         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8482             DisplayError(_("Bad FEN received from engine"), 0);
8483             return ;
8484         } else {
8485            Reset(TRUE, FALSE);
8486            CopyBoard(boards[0], initial_position);
8487            initialRulePlies = FENrulePlies;
8488            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8489            else gameMode = MachinePlaysBlack;
8490            DrawPosition(FALSE, boards[currentMove]);
8491         }
8492         return;
8493     }
8494
8495     /*
8496      * Look for communication commands
8497      */
8498     if (!strncmp(message, "telluser ", 9)) {
8499         if(message[9] == '\\' && message[10] == '\\')
8500             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8501         PlayTellSound();
8502         DisplayNote(message + 9);
8503         return;
8504     }
8505     if (!strncmp(message, "tellusererror ", 14)) {
8506         cps->userError = 1;
8507         if(message[14] == '\\' && message[15] == '\\')
8508             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8509         PlayTellSound();
8510         DisplayError(message + 14, 0);
8511         return;
8512     }
8513     if (!strncmp(message, "tellopponent ", 13)) {
8514       if (appData.icsActive) {
8515         if (loggedOn) {
8516           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8517           SendToICS(buf1);
8518         }
8519       } else {
8520         DisplayNote(message + 13);
8521       }
8522       return;
8523     }
8524     if (!strncmp(message, "tellothers ", 11)) {
8525       if (appData.icsActive) {
8526         if (loggedOn) {
8527           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8528           SendToICS(buf1);
8529         }
8530       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8531       return;
8532     }
8533     if (!strncmp(message, "tellall ", 8)) {
8534       if (appData.icsActive) {
8535         if (loggedOn) {
8536           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8537           SendToICS(buf1);
8538         }
8539       } else {
8540         DisplayNote(message + 8);
8541       }
8542       return;
8543     }
8544     if (strncmp(message, "warning", 7) == 0) {
8545         /* Undocumented feature, use tellusererror in new code */
8546         DisplayError(message, 0);
8547         return;
8548     }
8549     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8550         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8551         strcat(realname, " query");
8552         AskQuestion(realname, buf2, buf1, cps->pr);
8553         return;
8554     }
8555     /* Commands from the engine directly to ICS.  We don't allow these to be
8556      *  sent until we are logged on. Crafty kibitzes have been known to
8557      *  interfere with the login process.
8558      */
8559     if (loggedOn) {
8560         if (!strncmp(message, "tellics ", 8)) {
8561             SendToICS(message + 8);
8562             SendToICS("\n");
8563             return;
8564         }
8565         if (!strncmp(message, "tellicsnoalias ", 15)) {
8566             SendToICS(ics_prefix);
8567             SendToICS(message + 15);
8568             SendToICS("\n");
8569             return;
8570         }
8571         /* The following are for backward compatibility only */
8572         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8573             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8574             SendToICS(ics_prefix);
8575             SendToICS(message);
8576             SendToICS("\n");
8577             return;
8578         }
8579     }
8580     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8581         return;
8582     }
8583     /*
8584      * If the move is illegal, cancel it and redraw the board.
8585      * Also deal with other error cases.  Matching is rather loose
8586      * here to accommodate engines written before the spec.
8587      */
8588     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8589         strncmp(message, "Error", 5) == 0) {
8590         if (StrStr(message, "name") ||
8591             StrStr(message, "rating") || StrStr(message, "?") ||
8592             StrStr(message, "result") || StrStr(message, "board") ||
8593             StrStr(message, "bk") || StrStr(message, "computer") ||
8594             StrStr(message, "variant") || StrStr(message, "hint") ||
8595             StrStr(message, "random") || StrStr(message, "depth") ||
8596             StrStr(message, "accepted")) {
8597             return;
8598         }
8599         if (StrStr(message, "protover")) {
8600           /* Program is responding to input, so it's apparently done
8601              initializing, and this error message indicates it is
8602              protocol version 1.  So we don't need to wait any longer
8603              for it to initialize and send feature commands. */
8604           FeatureDone(cps, 1);
8605           cps->protocolVersion = 1;
8606           return;
8607         }
8608         cps->maybeThinking = FALSE;
8609
8610         if (StrStr(message, "draw")) {
8611             /* Program doesn't have "draw" command */
8612             cps->sendDrawOffers = 0;
8613             return;
8614         }
8615         if (cps->sendTime != 1 &&
8616             (StrStr(message, "time") || StrStr(message, "otim"))) {
8617           /* Program apparently doesn't have "time" or "otim" command */
8618           cps->sendTime = 0;
8619           return;
8620         }
8621         if (StrStr(message, "analyze")) {
8622             cps->analysisSupport = FALSE;
8623             cps->analyzing = FALSE;
8624 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8625             EditGameEvent(); // [HGM] try to preserve loaded game
8626             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8627             DisplayError(buf2, 0);
8628             return;
8629         }
8630         if (StrStr(message, "(no matching move)st")) {
8631           /* Special kludge for GNU Chess 4 only */
8632           cps->stKludge = TRUE;
8633           SendTimeControl(cps, movesPerSession, timeControl,
8634                           timeIncrement, appData.searchDepth,
8635                           searchTime);
8636           return;
8637         }
8638         if (StrStr(message, "(no matching move)sd")) {
8639           /* Special kludge for GNU Chess 4 only */
8640           cps->sdKludge = TRUE;
8641           SendTimeControl(cps, movesPerSession, timeControl,
8642                           timeIncrement, appData.searchDepth,
8643                           searchTime);
8644           return;
8645         }
8646         if (!StrStr(message, "llegal")) {
8647             return;
8648         }
8649         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8650             gameMode == IcsIdle) return;
8651         if (forwardMostMove <= backwardMostMove) return;
8652         if (pausing) PauseEvent();
8653       if(appData.forceIllegal) {
8654             // [HGM] illegal: machine refused move; force position after move into it
8655           SendToProgram("force\n", cps);
8656           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8657                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8658                 // when black is to move, while there might be nothing on a2 or black
8659                 // might already have the move. So send the board as if white has the move.
8660                 // But first we must change the stm of the engine, as it refused the last move
8661                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8662                 if(WhiteOnMove(forwardMostMove)) {
8663                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8664                     SendBoard(cps, forwardMostMove); // kludgeless board
8665                 } else {
8666                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8667                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8668                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8669                 }
8670           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8671             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8672                  gameMode == TwoMachinesPlay)
8673               SendToProgram("go\n", cps);
8674             return;
8675       } else
8676         if (gameMode == PlayFromGameFile) {
8677             /* Stop reading this game file */
8678             gameMode = EditGame;
8679             ModeHighlight();
8680         }
8681         /* [HGM] illegal-move claim should forfeit game when Xboard */
8682         /* only passes fully legal moves                            */
8683         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8684             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8685                                 "False illegal-move claim", GE_XBOARD );
8686             return; // do not take back move we tested as valid
8687         }
8688         currentMove = forwardMostMove-1;
8689         DisplayMove(currentMove-1); /* before DisplayMoveError */
8690         SwitchClocks(forwardMostMove-1); // [HGM] race
8691         DisplayBothClocks();
8692         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8693                 parseList[currentMove], _(cps->which));
8694         DisplayMoveError(buf1);
8695         DrawPosition(FALSE, boards[currentMove]);
8696
8697         SetUserThinkingEnables();
8698         return;
8699     }
8700     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8701         /* Program has a broken "time" command that
8702            outputs a string not ending in newline.
8703            Don't use it. */
8704         cps->sendTime = 0;
8705     }
8706
8707     /*
8708      * If chess program startup fails, exit with an error message.
8709      * Attempts to recover here are futile. [HGM] Well, we try anyway
8710      */
8711     if ((StrStr(message, "unknown host") != NULL)
8712         || (StrStr(message, "No remote directory") != NULL)
8713         || (StrStr(message, "not found") != NULL)
8714         || (StrStr(message, "No such file") != NULL)
8715         || (StrStr(message, "can't alloc") != NULL)
8716         || (StrStr(message, "Permission denied") != NULL)) {
8717
8718         cps->maybeThinking = FALSE;
8719         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8720                 _(cps->which), cps->program, cps->host, message);
8721         RemoveInputSource(cps->isr);
8722         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8723             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8724             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8725         }
8726         return;
8727     }
8728
8729     /*
8730      * Look for hint output
8731      */
8732     if (sscanf(message, "Hint: %s", buf1) == 1) {
8733         if (cps == &first && hintRequested) {
8734             hintRequested = FALSE;
8735             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8736                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8737                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8738                                     PosFlags(forwardMostMove),
8739                                     fromY, fromX, toY, toX, promoChar, buf1);
8740                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8741                 DisplayInformation(buf2);
8742             } else {
8743                 /* Hint move could not be parsed!? */
8744               snprintf(buf2, sizeof(buf2),
8745                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8746                         buf1, _(cps->which));
8747                 DisplayError(buf2, 0);
8748             }
8749         } else {
8750           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8751         }
8752         return;
8753     }
8754
8755     /*
8756      * Ignore other messages if game is not in progress
8757      */
8758     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8759         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8760
8761     /*
8762      * look for win, lose, draw, or draw offer
8763      */
8764     if (strncmp(message, "1-0", 3) == 0) {
8765         char *p, *q, *r = "";
8766         p = strchr(message, '{');
8767         if (p) {
8768             q = strchr(p, '}');
8769             if (q) {
8770                 *q = NULLCHAR;
8771                 r = p + 1;
8772             }
8773         }
8774         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8775         return;
8776     } else if (strncmp(message, "0-1", 3) == 0) {
8777         char *p, *q, *r = "";
8778         p = strchr(message, '{');
8779         if (p) {
8780             q = strchr(p, '}');
8781             if (q) {
8782                 *q = NULLCHAR;
8783                 r = p + 1;
8784             }
8785         }
8786         /* Kludge for Arasan 4.1 bug */
8787         if (strcmp(r, "Black resigns") == 0) {
8788             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8789             return;
8790         }
8791         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8792         return;
8793     } else if (strncmp(message, "1/2", 3) == 0) {
8794         char *p, *q, *r = "";
8795         p = strchr(message, '{');
8796         if (p) {
8797             q = strchr(p, '}');
8798             if (q) {
8799                 *q = NULLCHAR;
8800                 r = p + 1;
8801             }
8802         }
8803
8804         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8805         return;
8806
8807     } else if (strncmp(message, "White resign", 12) == 0) {
8808         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8809         return;
8810     } else if (strncmp(message, "Black resign", 12) == 0) {
8811         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8812         return;
8813     } else if (strncmp(message, "White matches", 13) == 0 ||
8814                strncmp(message, "Black matches", 13) == 0   ) {
8815         /* [HGM] ignore GNUShogi noises */
8816         return;
8817     } else if (strncmp(message, "White", 5) == 0 &&
8818                message[5] != '(' &&
8819                StrStr(message, "Black") == NULL) {
8820         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8821         return;
8822     } else if (strncmp(message, "Black", 5) == 0 &&
8823                message[5] != '(') {
8824         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8825         return;
8826     } else if (strcmp(message, "resign") == 0 ||
8827                strcmp(message, "computer resigns") == 0) {
8828         switch (gameMode) {
8829           case MachinePlaysBlack:
8830           case IcsPlayingBlack:
8831             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8832             break;
8833           case MachinePlaysWhite:
8834           case IcsPlayingWhite:
8835             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8836             break;
8837           case TwoMachinesPlay:
8838             if (cps->twoMachinesColor[0] == 'w')
8839               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8840             else
8841               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8842             break;
8843           default:
8844             /* can't happen */
8845             break;
8846         }
8847         return;
8848     } else if (strncmp(message, "opponent mates", 14) == 0) {
8849         switch (gameMode) {
8850           case MachinePlaysBlack:
8851           case IcsPlayingBlack:
8852             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8853             break;
8854           case MachinePlaysWhite:
8855           case IcsPlayingWhite:
8856             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8857             break;
8858           case TwoMachinesPlay:
8859             if (cps->twoMachinesColor[0] == 'w')
8860               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8861             else
8862               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8863             break;
8864           default:
8865             /* can't happen */
8866             break;
8867         }
8868         return;
8869     } else if (strncmp(message, "computer mates", 14) == 0) {
8870         switch (gameMode) {
8871           case MachinePlaysBlack:
8872           case IcsPlayingBlack:
8873             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8874             break;
8875           case MachinePlaysWhite:
8876           case IcsPlayingWhite:
8877             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8878             break;
8879           case TwoMachinesPlay:
8880             if (cps->twoMachinesColor[0] == 'w')
8881               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8882             else
8883               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8884             break;
8885           default:
8886             /* can't happen */
8887             break;
8888         }
8889         return;
8890     } else if (strncmp(message, "checkmate", 9) == 0) {
8891         if (WhiteOnMove(forwardMostMove)) {
8892             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8893         } else {
8894             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8895         }
8896         return;
8897     } else if (strstr(message, "Draw") != NULL ||
8898                strstr(message, "game is a draw") != NULL) {
8899         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8900         return;
8901     } else if (strstr(message, "offer") != NULL &&
8902                strstr(message, "draw") != NULL) {
8903 #if ZIPPY
8904         if (appData.zippyPlay && first.initDone) {
8905             /* Relay offer to ICS */
8906             SendToICS(ics_prefix);
8907             SendToICS("draw\n");
8908         }
8909 #endif
8910         cps->offeredDraw = 2; /* valid until this engine moves twice */
8911         if (gameMode == TwoMachinesPlay) {
8912             if (cps->other->offeredDraw) {
8913                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8914             /* [HGM] in two-machine mode we delay relaying draw offer      */
8915             /* until after we also have move, to see if it is really claim */
8916             }
8917         } else if (gameMode == MachinePlaysWhite ||
8918                    gameMode == MachinePlaysBlack) {
8919           if (userOfferedDraw) {
8920             DisplayInformation(_("Machine accepts your draw offer"));
8921             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8922           } else {
8923             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8924           }
8925         }
8926     }
8927
8928
8929     /*
8930      * Look for thinking output
8931      */
8932     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8933           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8934                                 ) {
8935         int plylev, mvleft, mvtot, curscore, time;
8936         char mvname[MOVE_LEN];
8937         u64 nodes; // [DM]
8938         char plyext;
8939         int ignore = FALSE;
8940         int prefixHint = FALSE;
8941         mvname[0] = NULLCHAR;
8942
8943         switch (gameMode) {
8944           case MachinePlaysBlack:
8945           case IcsPlayingBlack:
8946             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8947             break;
8948           case MachinePlaysWhite:
8949           case IcsPlayingWhite:
8950             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8951             break;
8952           case AnalyzeMode:
8953           case AnalyzeFile:
8954             break;
8955           case IcsObserving: /* [DM] icsEngineAnalyze */
8956             if (!appData.icsEngineAnalyze) ignore = TRUE;
8957             break;
8958           case TwoMachinesPlay:
8959             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8960                 ignore = TRUE;
8961             }
8962             break;
8963           default:
8964             ignore = TRUE;
8965             break;
8966         }
8967
8968         if (!ignore) {
8969             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8970             buf1[0] = NULLCHAR;
8971             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8972                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8973
8974                 if (plyext != ' ' && plyext != '\t') {
8975                     time *= 100;
8976                 }
8977
8978                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8979                 if( cps->scoreIsAbsolute &&
8980                     ( gameMode == MachinePlaysBlack ||
8981                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8982                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8983                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8984                      !WhiteOnMove(currentMove)
8985                     ) )
8986                 {
8987                     curscore = -curscore;
8988                 }
8989
8990                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8991
8992                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8993                         char buf[MSG_SIZ];
8994                         FILE *f;
8995                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8996                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8997                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8998                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8999                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9000                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9001                                 fclose(f);
9002                         } else DisplayError(_("failed writing PV"), 0);
9003                 }
9004
9005                 tempStats.depth = plylev;
9006                 tempStats.nodes = nodes;
9007                 tempStats.time = time;
9008                 tempStats.score = curscore;
9009                 tempStats.got_only_move = 0;
9010
9011                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9012                         int ticklen;
9013
9014                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9015                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9016                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9017                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9018                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9019                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9020                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9021                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9022                 }
9023
9024                 /* Buffer overflow protection */
9025                 if (pv[0] != NULLCHAR) {
9026                     if (strlen(pv) >= sizeof(tempStats.movelist)
9027                         && appData.debugMode) {
9028                         fprintf(debugFP,
9029                                 "PV is too long; using the first %u bytes.\n",
9030                                 (unsigned) sizeof(tempStats.movelist) - 1);
9031                     }
9032
9033                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9034                 } else {
9035                     sprintf(tempStats.movelist, " no PV\n");
9036                 }
9037
9038                 if (tempStats.seen_stat) {
9039                     tempStats.ok_to_send = 1;
9040                 }
9041
9042                 if (strchr(tempStats.movelist, '(') != NULL) {
9043                     tempStats.line_is_book = 1;
9044                     tempStats.nr_moves = 0;
9045                     tempStats.moves_left = 0;
9046                 } else {
9047                     tempStats.line_is_book = 0;
9048                 }
9049
9050                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9051                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9052
9053                 SendProgramStatsToFrontend( cps, &tempStats );
9054
9055                 /*
9056                     [AS] Protect the thinkOutput buffer from overflow... this
9057                     is only useful if buf1 hasn't overflowed first!
9058                 */
9059                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9060                          plylev,
9061                          (gameMode == TwoMachinesPlay ?
9062                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9063                          ((double) curscore) / 100.0,
9064                          prefixHint ? lastHint : "",
9065                          prefixHint ? " " : "" );
9066
9067                 if( buf1[0] != NULLCHAR ) {
9068                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9069
9070                     if( strlen(pv) > max_len ) {
9071                         if( appData.debugMode) {
9072                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9073                         }
9074                         pv[max_len+1] = '\0';
9075                     }
9076
9077                     strcat( thinkOutput, pv);
9078                 }
9079
9080                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9081                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9082                     DisplayMove(currentMove - 1);
9083                 }
9084                 return;
9085
9086             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9087                 /* crafty (9.25+) says "(only move) <move>"
9088                  * if there is only 1 legal move
9089                  */
9090                 sscanf(p, "(only move) %s", buf1);
9091                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9092                 sprintf(programStats.movelist, "%s (only move)", buf1);
9093                 programStats.depth = 1;
9094                 programStats.nr_moves = 1;
9095                 programStats.moves_left = 1;
9096                 programStats.nodes = 1;
9097                 programStats.time = 1;
9098                 programStats.got_only_move = 1;
9099
9100                 /* Not really, but we also use this member to
9101                    mean "line isn't going to change" (Crafty
9102                    isn't searching, so stats won't change) */
9103                 programStats.line_is_book = 1;
9104
9105                 SendProgramStatsToFrontend( cps, &programStats );
9106
9107                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9108                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9109                     DisplayMove(currentMove - 1);
9110                 }
9111                 return;
9112             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9113                               &time, &nodes, &plylev, &mvleft,
9114                               &mvtot, mvname) >= 5) {
9115                 /* The stat01: line is from Crafty (9.29+) in response
9116                    to the "." command */
9117                 programStats.seen_stat = 1;
9118                 cps->maybeThinking = TRUE;
9119
9120                 if (programStats.got_only_move || !appData.periodicUpdates)
9121                   return;
9122
9123                 programStats.depth = plylev;
9124                 programStats.time = time;
9125                 programStats.nodes = nodes;
9126                 programStats.moves_left = mvleft;
9127                 programStats.nr_moves = mvtot;
9128                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9129                 programStats.ok_to_send = 1;
9130                 programStats.movelist[0] = '\0';
9131
9132                 SendProgramStatsToFrontend( cps, &programStats );
9133
9134                 return;
9135
9136             } else if (strncmp(message,"++",2) == 0) {
9137                 /* Crafty 9.29+ outputs this */
9138                 programStats.got_fail = 2;
9139                 return;
9140
9141             } else if (strncmp(message,"--",2) == 0) {
9142                 /* Crafty 9.29+ outputs this */
9143                 programStats.got_fail = 1;
9144                 return;
9145
9146             } else if (thinkOutput[0] != NULLCHAR &&
9147                        strncmp(message, "    ", 4) == 0) {
9148                 unsigned message_len;
9149
9150                 p = message;
9151                 while (*p && *p == ' ') p++;
9152
9153                 message_len = strlen( p );
9154
9155                 /* [AS] Avoid buffer overflow */
9156                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9157                     strcat(thinkOutput, " ");
9158                     strcat(thinkOutput, p);
9159                 }
9160
9161                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9162                     strcat(programStats.movelist, " ");
9163                     strcat(programStats.movelist, p);
9164                 }
9165
9166                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9167                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9168                     DisplayMove(currentMove - 1);
9169                 }
9170                 return;
9171             }
9172         }
9173         else {
9174             buf1[0] = NULLCHAR;
9175
9176             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9177                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9178             {
9179                 ChessProgramStats cpstats;
9180
9181                 if (plyext != ' ' && plyext != '\t') {
9182                     time *= 100;
9183                 }
9184
9185                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9186                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9187                     curscore = -curscore;
9188                 }
9189
9190                 cpstats.depth = plylev;
9191                 cpstats.nodes = nodes;
9192                 cpstats.time = time;
9193                 cpstats.score = curscore;
9194                 cpstats.got_only_move = 0;
9195                 cpstats.movelist[0] = '\0';
9196
9197                 if (buf1[0] != NULLCHAR) {
9198                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9199                 }
9200
9201                 cpstats.ok_to_send = 0;
9202                 cpstats.line_is_book = 0;
9203                 cpstats.nr_moves = 0;
9204                 cpstats.moves_left = 0;
9205
9206                 SendProgramStatsToFrontend( cps, &cpstats );
9207             }
9208         }
9209     }
9210 }
9211
9212
9213 /* Parse a game score from the character string "game", and
9214    record it as the history of the current game.  The game
9215    score is NOT assumed to start from the standard position.
9216    The display is not updated in any way.
9217    */
9218 void
9219 ParseGameHistory (char *game)
9220 {
9221     ChessMove moveType;
9222     int fromX, fromY, toX, toY, boardIndex;
9223     char promoChar;
9224     char *p, *q;
9225     char buf[MSG_SIZ];
9226
9227     if (appData.debugMode)
9228       fprintf(debugFP, "Parsing game history: %s\n", game);
9229
9230     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9231     gameInfo.site = StrSave(appData.icsHost);
9232     gameInfo.date = PGNDate();
9233     gameInfo.round = StrSave("-");
9234
9235     /* Parse out names of players */
9236     while (*game == ' ') game++;
9237     p = buf;
9238     while (*game != ' ') *p++ = *game++;
9239     *p = NULLCHAR;
9240     gameInfo.white = StrSave(buf);
9241     while (*game == ' ') game++;
9242     p = buf;
9243     while (*game != ' ' && *game != '\n') *p++ = *game++;
9244     *p = NULLCHAR;
9245     gameInfo.black = StrSave(buf);
9246
9247     /* Parse moves */
9248     boardIndex = blackPlaysFirst ? 1 : 0;
9249     yynewstr(game);
9250     for (;;) {
9251         yyboardindex = boardIndex;
9252         moveType = (ChessMove) Myylex();
9253         switch (moveType) {
9254           case IllegalMove:             /* maybe suicide chess, etc. */
9255   if (appData.debugMode) {
9256     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9257     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9258     setbuf(debugFP, NULL);
9259   }
9260           case WhitePromotion:
9261           case BlackPromotion:
9262           case WhiteNonPromotion:
9263           case BlackNonPromotion:
9264           case NormalMove:
9265           case WhiteCapturesEnPassant:
9266           case BlackCapturesEnPassant:
9267           case WhiteKingSideCastle:
9268           case WhiteQueenSideCastle:
9269           case BlackKingSideCastle:
9270           case BlackQueenSideCastle:
9271           case WhiteKingSideCastleWild:
9272           case WhiteQueenSideCastleWild:
9273           case BlackKingSideCastleWild:
9274           case BlackQueenSideCastleWild:
9275           /* PUSH Fabien */
9276           case WhiteHSideCastleFR:
9277           case WhiteASideCastleFR:
9278           case BlackHSideCastleFR:
9279           case BlackASideCastleFR:
9280           /* POP Fabien */
9281             fromX = currentMoveString[0] - AAA;
9282             fromY = currentMoveString[1] - ONE;
9283             toX = currentMoveString[2] - AAA;
9284             toY = currentMoveString[3] - ONE;
9285             promoChar = currentMoveString[4];
9286             break;
9287           case WhiteDrop:
9288           case BlackDrop:
9289             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9290             fromX = moveType == WhiteDrop ?
9291               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9292             (int) CharToPiece(ToLower(currentMoveString[0]));
9293             fromY = DROP_RANK;
9294             toX = currentMoveString[2] - AAA;
9295             toY = currentMoveString[3] - ONE;
9296             promoChar = NULLCHAR;
9297             break;
9298           case AmbiguousMove:
9299             /* bug? */
9300             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9301   if (appData.debugMode) {
9302     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9303     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9304     setbuf(debugFP, NULL);
9305   }
9306             DisplayError(buf, 0);
9307             return;
9308           case ImpossibleMove:
9309             /* bug? */
9310             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9311   if (appData.debugMode) {
9312     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9313     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9314     setbuf(debugFP, NULL);
9315   }
9316             DisplayError(buf, 0);
9317             return;
9318           case EndOfFile:
9319             if (boardIndex < backwardMostMove) {
9320                 /* Oops, gap.  How did that happen? */
9321                 DisplayError(_("Gap in move list"), 0);
9322                 return;
9323             }
9324             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9325             if (boardIndex > forwardMostMove) {
9326                 forwardMostMove = boardIndex;
9327             }
9328             return;
9329           case ElapsedTime:
9330             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9331                 strcat(parseList[boardIndex-1], " ");
9332                 strcat(parseList[boardIndex-1], yy_text);
9333             }
9334             continue;
9335           case Comment:
9336           case PGNTag:
9337           case NAG:
9338           default:
9339             /* ignore */
9340             continue;
9341           case WhiteWins:
9342           case BlackWins:
9343           case GameIsDrawn:
9344           case GameUnfinished:
9345             if (gameMode == IcsExamining) {
9346                 if (boardIndex < backwardMostMove) {
9347                     /* Oops, gap.  How did that happen? */
9348                     return;
9349                 }
9350                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9351                 return;
9352             }
9353             gameInfo.result = moveType;
9354             p = strchr(yy_text, '{');
9355             if (p == NULL) p = strchr(yy_text, '(');
9356             if (p == NULL) {
9357                 p = yy_text;
9358                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9359             } else {
9360                 q = strchr(p, *p == '{' ? '}' : ')');
9361                 if (q != NULL) *q = NULLCHAR;
9362                 p++;
9363             }
9364             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9365             gameInfo.resultDetails = StrSave(p);
9366             continue;
9367         }
9368         if (boardIndex >= forwardMostMove &&
9369             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9370             backwardMostMove = blackPlaysFirst ? 1 : 0;
9371             return;
9372         }
9373         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9374                                  fromY, fromX, toY, toX, promoChar,
9375                                  parseList[boardIndex]);
9376         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9377         /* currentMoveString is set as a side-effect of yylex */
9378         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9379         strcat(moveList[boardIndex], "\n");
9380         boardIndex++;
9381         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9382         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9383           case MT_NONE:
9384           case MT_STALEMATE:
9385           default:
9386             break;
9387           case MT_CHECK:
9388             if(gameInfo.variant != VariantShogi)
9389                 strcat(parseList[boardIndex - 1], "+");
9390             break;
9391           case MT_CHECKMATE:
9392           case MT_STAINMATE:
9393             strcat(parseList[boardIndex - 1], "#");
9394             break;
9395         }
9396     }
9397 }
9398
9399
9400 /* Apply a move to the given board  */
9401 void
9402 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9403 {
9404   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9405   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9406
9407     /* [HGM] compute & store e.p. status and castling rights for new position */
9408     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9409
9410       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9411       oldEP = (signed char)board[EP_STATUS];
9412       board[EP_STATUS] = EP_NONE;
9413
9414   if (fromY == DROP_RANK) {
9415         /* must be first */
9416         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9417             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9418             return;
9419         }
9420         piece = board[toY][toX] = (ChessSquare) fromX;
9421   } else {
9422       int i;
9423
9424       if( board[toY][toX] != EmptySquare )
9425            board[EP_STATUS] = EP_CAPTURE;
9426
9427       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9428            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9429                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9430       } else
9431       if( board[fromY][fromX] == WhitePawn ) {
9432            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9433                board[EP_STATUS] = EP_PAWN_MOVE;
9434            if( toY-fromY==2) {
9435                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9436                         gameInfo.variant != VariantBerolina || toX < fromX)
9437                       board[EP_STATUS] = toX | berolina;
9438                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9439                         gameInfo.variant != VariantBerolina || toX > fromX)
9440                       board[EP_STATUS] = toX;
9441            }
9442       } else
9443       if( board[fromY][fromX] == BlackPawn ) {
9444            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9445                board[EP_STATUS] = EP_PAWN_MOVE;
9446            if( toY-fromY== -2) {
9447                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9448                         gameInfo.variant != VariantBerolina || toX < fromX)
9449                       board[EP_STATUS] = toX | berolina;
9450                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9451                         gameInfo.variant != VariantBerolina || toX > fromX)
9452                       board[EP_STATUS] = toX;
9453            }
9454        }
9455
9456        for(i=0; i<nrCastlingRights; i++) {
9457            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9458               board[CASTLING][i] == toX   && castlingRank[i] == toY
9459              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9460        }
9461
9462        if(gameInfo.variant == VariantSChess) { // update virginity
9463            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9464            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9465            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9466            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9467        }
9468
9469      if (fromX == toX && fromY == toY) return;
9470
9471      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9472      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9473      if(gameInfo.variant == VariantKnightmate)
9474          king += (int) WhiteUnicorn - (int) WhiteKing;
9475
9476     /* Code added by Tord: */
9477     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9478     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9479         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9480       board[fromY][fromX] = EmptySquare;
9481       board[toY][toX] = EmptySquare;
9482       if((toX > fromX) != (piece == WhiteRook)) {
9483         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9484       } else {
9485         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9486       }
9487     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9488                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9489       board[fromY][fromX] = EmptySquare;
9490       board[toY][toX] = EmptySquare;
9491       if((toX > fromX) != (piece == BlackRook)) {
9492         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9493       } else {
9494         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9495       }
9496     /* End of code added by Tord */
9497
9498     } else if (board[fromY][fromX] == king
9499         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9500         && toY == fromY && toX > fromX+1) {
9501         board[fromY][fromX] = EmptySquare;
9502         board[toY][toX] = king;
9503         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9504         board[fromY][BOARD_RGHT-1] = EmptySquare;
9505     } else if (board[fromY][fromX] == king
9506         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9507                && toY == fromY && toX < fromX-1) {
9508         board[fromY][fromX] = EmptySquare;
9509         board[toY][toX] = king;
9510         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9511         board[fromY][BOARD_LEFT] = EmptySquare;
9512     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9513                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9514                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9515                ) {
9516         /* white pawn promotion */
9517         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9518         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9519             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9520         board[fromY][fromX] = EmptySquare;
9521     } else if ((fromY >= BOARD_HEIGHT>>1)
9522                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9523                && (toX != fromX)
9524                && gameInfo.variant != VariantXiangqi
9525                && gameInfo.variant != VariantBerolina
9526                && (board[fromY][fromX] == WhitePawn)
9527                && (board[toY][toX] == EmptySquare)) {
9528         board[fromY][fromX] = EmptySquare;
9529         board[toY][toX] = WhitePawn;
9530         captured = board[toY - 1][toX];
9531         board[toY - 1][toX] = EmptySquare;
9532     } else if ((fromY == BOARD_HEIGHT-4)
9533                && (toX == fromX)
9534                && gameInfo.variant == VariantBerolina
9535                && (board[fromY][fromX] == WhitePawn)
9536                && (board[toY][toX] == EmptySquare)) {
9537         board[fromY][fromX] = EmptySquare;
9538         board[toY][toX] = WhitePawn;
9539         if(oldEP & EP_BEROLIN_A) {
9540                 captured = board[fromY][fromX-1];
9541                 board[fromY][fromX-1] = EmptySquare;
9542         }else{  captured = board[fromY][fromX+1];
9543                 board[fromY][fromX+1] = EmptySquare;
9544         }
9545     } else if (board[fromY][fromX] == king
9546         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9547                && toY == fromY && toX > fromX+1) {
9548         board[fromY][fromX] = EmptySquare;
9549         board[toY][toX] = king;
9550         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9551         board[fromY][BOARD_RGHT-1] = EmptySquare;
9552     } else if (board[fromY][fromX] == king
9553         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9554                && toY == fromY && toX < fromX-1) {
9555         board[fromY][fromX] = EmptySquare;
9556         board[toY][toX] = king;
9557         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9558         board[fromY][BOARD_LEFT] = EmptySquare;
9559     } else if (fromY == 7 && fromX == 3
9560                && board[fromY][fromX] == BlackKing
9561                && toY == 7 && toX == 5) {
9562         board[fromY][fromX] = EmptySquare;
9563         board[toY][toX] = BlackKing;
9564         board[fromY][7] = EmptySquare;
9565         board[toY][4] = BlackRook;
9566     } else if (fromY == 7 && fromX == 3
9567                && board[fromY][fromX] == BlackKing
9568                && toY == 7 && toX == 1) {
9569         board[fromY][fromX] = EmptySquare;
9570         board[toY][toX] = BlackKing;
9571         board[fromY][0] = EmptySquare;
9572         board[toY][2] = BlackRook;
9573     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9574                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9575                && toY < promoRank && promoChar
9576                ) {
9577         /* black pawn promotion */
9578         board[toY][toX] = CharToPiece(ToLower(promoChar));
9579         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9580             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9581         board[fromY][fromX] = EmptySquare;
9582     } else if ((fromY < BOARD_HEIGHT>>1)
9583                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9584                && (toX != fromX)
9585                && gameInfo.variant != VariantXiangqi
9586                && gameInfo.variant != VariantBerolina
9587                && (board[fromY][fromX] == BlackPawn)
9588                && (board[toY][toX] == EmptySquare)) {
9589         board[fromY][fromX] = EmptySquare;
9590         board[toY][toX] = BlackPawn;
9591         captured = board[toY + 1][toX];
9592         board[toY + 1][toX] = EmptySquare;
9593     } else if ((fromY == 3)
9594                && (toX == fromX)
9595                && gameInfo.variant == VariantBerolina
9596                && (board[fromY][fromX] == BlackPawn)
9597                && (board[toY][toX] == EmptySquare)) {
9598         board[fromY][fromX] = EmptySquare;
9599         board[toY][toX] = BlackPawn;
9600         if(oldEP & EP_BEROLIN_A) {
9601                 captured = board[fromY][fromX-1];
9602                 board[fromY][fromX-1] = EmptySquare;
9603         }else{  captured = board[fromY][fromX+1];
9604                 board[fromY][fromX+1] = EmptySquare;
9605         }
9606     } else {
9607         board[toY][toX] = board[fromY][fromX];
9608         board[fromY][fromX] = EmptySquare;
9609     }
9610   }
9611
9612     if (gameInfo.holdingsWidth != 0) {
9613
9614       /* !!A lot more code needs to be written to support holdings  */
9615       /* [HGM] OK, so I have written it. Holdings are stored in the */
9616       /* penultimate board files, so they are automaticlly stored   */
9617       /* in the game history.                                       */
9618       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9619                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9620         /* Delete from holdings, by decreasing count */
9621         /* and erasing image if necessary            */
9622         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9623         if(p < (int) BlackPawn) { /* white drop */
9624              p -= (int)WhitePawn;
9625                  p = PieceToNumber((ChessSquare)p);
9626              if(p >= gameInfo.holdingsSize) p = 0;
9627              if(--board[p][BOARD_WIDTH-2] <= 0)
9628                   board[p][BOARD_WIDTH-1] = EmptySquare;
9629              if((int)board[p][BOARD_WIDTH-2] < 0)
9630                         board[p][BOARD_WIDTH-2] = 0;
9631         } else {                  /* black drop */
9632              p -= (int)BlackPawn;
9633                  p = PieceToNumber((ChessSquare)p);
9634              if(p >= gameInfo.holdingsSize) p = 0;
9635              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9636                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9637              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9638                         board[BOARD_HEIGHT-1-p][1] = 0;
9639         }
9640       }
9641       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9642           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9643         /* [HGM] holdings: Add to holdings, if holdings exist */
9644         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9645                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9646                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9647         }
9648         p = (int) captured;
9649         if (p >= (int) BlackPawn) {
9650           p -= (int)BlackPawn;
9651           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9652                   /* in Shogi restore piece to its original  first */
9653                   captured = (ChessSquare) (DEMOTED captured);
9654                   p = DEMOTED p;
9655           }
9656           p = PieceToNumber((ChessSquare)p);
9657           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9658           board[p][BOARD_WIDTH-2]++;
9659           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9660         } else {
9661           p -= (int)WhitePawn;
9662           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9663                   captured = (ChessSquare) (DEMOTED captured);
9664                   p = DEMOTED p;
9665           }
9666           p = PieceToNumber((ChessSquare)p);
9667           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9668           board[BOARD_HEIGHT-1-p][1]++;
9669           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9670         }
9671       }
9672     } else if (gameInfo.variant == VariantAtomic) {
9673       if (captured != EmptySquare) {
9674         int y, x;
9675         for (y = toY-1; y <= toY+1; y++) {
9676           for (x = toX-1; x <= toX+1; x++) {
9677             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9678                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9679               board[y][x] = EmptySquare;
9680             }
9681           }
9682         }
9683         board[toY][toX] = EmptySquare;
9684       }
9685     }
9686     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9687         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9688     } else
9689     if(promoChar == '+') {
9690         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9691         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9692     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9693         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9694         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9695            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9696         board[toY][toX] = newPiece;
9697     }
9698     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9699                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9700         // [HGM] superchess: take promotion piece out of holdings
9701         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9702         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9703             if(!--board[k][BOARD_WIDTH-2])
9704                 board[k][BOARD_WIDTH-1] = EmptySquare;
9705         } else {
9706             if(!--board[BOARD_HEIGHT-1-k][1])
9707                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9708         }
9709     }
9710
9711 }
9712
9713 /* Updates forwardMostMove */
9714 void
9715 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9716 {
9717 //    forwardMostMove++; // [HGM] bare: moved downstream
9718
9719     (void) CoordsToAlgebraic(boards[forwardMostMove],
9720                              PosFlags(forwardMostMove),
9721                              fromY, fromX, toY, toX, promoChar,
9722                              parseList[forwardMostMove]);
9723
9724     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9725         int timeLeft; static int lastLoadFlag=0; int king, piece;
9726         piece = boards[forwardMostMove][fromY][fromX];
9727         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9728         if(gameInfo.variant == VariantKnightmate)
9729             king += (int) WhiteUnicorn - (int) WhiteKing;
9730         if(forwardMostMove == 0) {
9731             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9732                 fprintf(serverMoves, "%s;", UserName());
9733             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9734                 fprintf(serverMoves, "%s;", second.tidy);
9735             fprintf(serverMoves, "%s;", first.tidy);
9736             if(gameMode == MachinePlaysWhite)
9737                 fprintf(serverMoves, "%s;", UserName());
9738             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9739                 fprintf(serverMoves, "%s;", second.tidy);
9740         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9741         lastLoadFlag = loadFlag;
9742         // print base move
9743         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9744         // print castling suffix
9745         if( toY == fromY && piece == king ) {
9746             if(toX-fromX > 1)
9747                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9748             if(fromX-toX >1)
9749                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9750         }
9751         // e.p. suffix
9752         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9753              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9754              boards[forwardMostMove][toY][toX] == EmptySquare
9755              && fromX != toX && fromY != toY)
9756                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9757         // promotion suffix
9758         if(promoChar != NULLCHAR) {
9759             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9760                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9761                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9762             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9763         }
9764         if(!loadFlag) {
9765                 char buf[MOVE_LEN*2], *p; int len;
9766             fprintf(serverMoves, "/%d/%d",
9767                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9768             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9769             else                      timeLeft = blackTimeRemaining/1000;
9770             fprintf(serverMoves, "/%d", timeLeft);
9771                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9772                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9773                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9774                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9775             fprintf(serverMoves, "/%s", buf);
9776         }
9777         fflush(serverMoves);
9778     }
9779
9780     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9781         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9782       return;
9783     }
9784     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9785     if (commentList[forwardMostMove+1] != NULL) {
9786         free(commentList[forwardMostMove+1]);
9787         commentList[forwardMostMove+1] = NULL;
9788     }
9789     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9790     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9791     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9792     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9793     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9794     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9795     adjustedClock = FALSE;
9796     gameInfo.result = GameUnfinished;
9797     if (gameInfo.resultDetails != NULL) {
9798         free(gameInfo.resultDetails);
9799         gameInfo.resultDetails = NULL;
9800     }
9801     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9802                               moveList[forwardMostMove - 1]);
9803     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9804       case MT_NONE:
9805       case MT_STALEMATE:
9806       default:
9807         break;
9808       case MT_CHECK:
9809         if(gameInfo.variant != VariantShogi)
9810             strcat(parseList[forwardMostMove - 1], "+");
9811         break;
9812       case MT_CHECKMATE:
9813       case MT_STAINMATE:
9814         strcat(parseList[forwardMostMove - 1], "#");
9815         break;
9816     }
9817
9818 }
9819
9820 /* Updates currentMove if not pausing */
9821 void
9822 ShowMove (int fromX, int fromY, int toX, int toY)
9823 {
9824     int instant = (gameMode == PlayFromGameFile) ?
9825         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9826     if(appData.noGUI) return;
9827     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9828         if (!instant) {
9829             if (forwardMostMove == currentMove + 1) {
9830                 AnimateMove(boards[forwardMostMove - 1],
9831                             fromX, fromY, toX, toY);
9832             }
9833         }
9834         currentMove = forwardMostMove;
9835     }
9836
9837     if (instant) return;
9838
9839     DisplayMove(currentMove - 1);
9840     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9841             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9842                 SetHighlights(fromX, fromY, toX, toY);
9843             }
9844     }
9845     DrawPosition(FALSE, boards[currentMove]);
9846     DisplayBothClocks();
9847     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9848 }
9849
9850 void
9851 SendEgtPath (ChessProgramState *cps)
9852 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9853         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9854
9855         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9856
9857         while(*p) {
9858             char c, *q = name+1, *r, *s;
9859
9860             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9861             while(*p && *p != ',') *q++ = *p++;
9862             *q++ = ':'; *q = 0;
9863             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9864                 strcmp(name, ",nalimov:") == 0 ) {
9865                 // take nalimov path from the menu-changeable option first, if it is defined
9866               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9867                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9868             } else
9869             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9870                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9871                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9872                 s = r = StrStr(s, ":") + 1; // beginning of path info
9873                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9874                 c = *r; *r = 0;             // temporarily null-terminate path info
9875                     *--q = 0;               // strip of trailig ':' from name
9876                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9877                 *r = c;
9878                 SendToProgram(buf,cps);     // send egtbpath command for this format
9879             }
9880             if(*p == ',') p++; // read away comma to position for next format name
9881         }
9882 }
9883
9884 static int
9885 NonStandardBoardSize ()
9886 {
9887       /* [HGM] Awkward testing. Should really be a table */
9888       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9889       if( gameInfo.variant == VariantXiangqi )
9890            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9891       if( gameInfo.variant == VariantShogi )
9892            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9893       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9894            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9895       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9896           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9897            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9898       if( gameInfo.variant == VariantCourier )
9899            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9900       if( gameInfo.variant == VariantSuper )
9901            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9902       if( gameInfo.variant == VariantGreat )
9903            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9904       if( gameInfo.variant == VariantSChess )
9905            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9906       if( gameInfo.variant == VariantGrand )
9907            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9908       return overruled;
9909 }
9910
9911 void
9912 InitChessProgram (ChessProgramState *cps, int setup)
9913 /* setup needed to setup FRC opening position */
9914 {
9915     char buf[MSG_SIZ], b[MSG_SIZ];
9916     if (appData.noChessProgram) return;
9917     hintRequested = FALSE;
9918     bookRequested = FALSE;
9919
9920     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9921     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9922     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9923     if(cps->memSize) { /* [HGM] memory */
9924       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9925         SendToProgram(buf, cps);
9926     }
9927     SendEgtPath(cps); /* [HGM] EGT */
9928     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9929       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9930         SendToProgram(buf, cps);
9931     }
9932
9933     SendToProgram(cps->initString, cps);
9934     if (gameInfo.variant != VariantNormal &&
9935         gameInfo.variant != VariantLoadable
9936         /* [HGM] also send variant if board size non-standard */
9937         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9938                                             ) {
9939       char *v = VariantName(gameInfo.variant);
9940       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9941         /* [HGM] in protocol 1 we have to assume all variants valid */
9942         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9943         DisplayFatalError(buf, 0, 1);
9944         return;
9945       }
9946
9947       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
9948         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9949                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9950            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9951            if(StrStr(cps->variants, b) == NULL) {
9952                // specific sized variant not known, check if general sizing allowed
9953                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9954                    if(StrStr(cps->variants, "boardsize") == NULL) {
9955                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9956                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9957                        DisplayFatalError(buf, 0, 1);
9958                        return;
9959                    }
9960                    /* [HGM] here we really should compare with the maximum supported board size */
9961                }
9962            }
9963       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9964       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9965       SendToProgram(buf, cps);
9966     }
9967     currentlyInitializedVariant = gameInfo.variant;
9968
9969     /* [HGM] send opening position in FRC to first engine */
9970     if(setup) {
9971           SendToProgram("force\n", cps);
9972           SendBoard(cps, 0);
9973           /* engine is now in force mode! Set flag to wake it up after first move. */
9974           setboardSpoiledMachineBlack = 1;
9975     }
9976
9977     if (cps->sendICS) {
9978       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9979       SendToProgram(buf, cps);
9980     }
9981     cps->maybeThinking = FALSE;
9982     cps->offeredDraw = 0;
9983     if (!appData.icsActive) {
9984         SendTimeControl(cps, movesPerSession, timeControl,
9985                         timeIncrement, appData.searchDepth,
9986                         searchTime);
9987     }
9988     if (appData.showThinking
9989         // [HGM] thinking: four options require thinking output to be sent
9990         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9991                                 ) {
9992         SendToProgram("post\n", cps);
9993     }
9994     SendToProgram("hard\n", cps);
9995     if (!appData.ponderNextMove) {
9996         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9997            it without being sure what state we are in first.  "hard"
9998            is not a toggle, so that one is OK.
9999          */
10000         SendToProgram("easy\n", cps);
10001     }
10002     if (cps->usePing) {
10003       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10004       SendToProgram(buf, cps);
10005     }
10006     cps->initDone = TRUE;
10007     ClearEngineOutputPane(cps == &second);
10008 }
10009
10010
10011 void
10012 ResendOptions (ChessProgramState *cps)
10013 { // send the stored value of the options
10014   int i;
10015   char buf[MSG_SIZ];
10016   Option *opt = cps->option;
10017   for(i=0; i<cps->nrOptions; i++, opt++) {
10018       switch(opt->type) {
10019         case Spin:
10020         case Slider:
10021         case CheckBox:
10022             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10023           break;
10024         case ComboBox:
10025           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10026           break;
10027         default:
10028             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10029           break;
10030         case Button:
10031         case SaveButton:
10032           continue;
10033       }
10034       SendToProgram(buf, cps);
10035   }
10036 }
10037
10038 void
10039 StartChessProgram (ChessProgramState *cps)
10040 {
10041     char buf[MSG_SIZ];
10042     int err;
10043
10044     if (appData.noChessProgram) return;
10045     cps->initDone = FALSE;
10046
10047     if (strcmp(cps->host, "localhost") == 0) {
10048         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10049     } else if (*appData.remoteShell == NULLCHAR) {
10050         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10051     } else {
10052         if (*appData.remoteUser == NULLCHAR) {
10053           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10054                     cps->program);
10055         } else {
10056           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10057                     cps->host, appData.remoteUser, cps->program);
10058         }
10059         err = StartChildProcess(buf, "", &cps->pr);
10060     }
10061
10062     if (err != 0) {
10063       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10064         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10065         if(cps != &first) return;
10066         appData.noChessProgram = TRUE;
10067         ThawUI();
10068         SetNCPMode();
10069 //      DisplayFatalError(buf, err, 1);
10070 //      cps->pr = NoProc;
10071 //      cps->isr = NULL;
10072         return;
10073     }
10074
10075     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10076     if (cps->protocolVersion > 1) {
10077       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10078       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10079         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10080         cps->comboCnt = 0;  //                and values of combo boxes
10081       }
10082       SendToProgram(buf, cps);
10083       if(cps->reload) ResendOptions(cps);
10084     } else {
10085       SendToProgram("xboard\n", cps);
10086     }
10087 }
10088
10089 void
10090 TwoMachinesEventIfReady P((void))
10091 {
10092   static int curMess = 0;
10093   if (first.lastPing != first.lastPong) {
10094     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10095     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10096     return;
10097   }
10098   if (second.lastPing != second.lastPong) {
10099     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10100     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10101     return;
10102   }
10103   DisplayMessage("", ""); curMess = 0;
10104   TwoMachinesEvent();
10105 }
10106
10107 char *
10108 MakeName (char *template)
10109 {
10110     time_t clock;
10111     struct tm *tm;
10112     static char buf[MSG_SIZ];
10113     char *p = buf;
10114     int i;
10115
10116     clock = time((time_t *)NULL);
10117     tm = localtime(&clock);
10118
10119     while(*p++ = *template++) if(p[-1] == '%') {
10120         switch(*template++) {
10121           case 0:   *p = 0; return buf;
10122           case 'Y': i = tm->tm_year+1900; break;
10123           case 'y': i = tm->tm_year-100; break;
10124           case 'M': i = tm->tm_mon+1; break;
10125           case 'd': i = tm->tm_mday; break;
10126           case 'h': i = tm->tm_hour; break;
10127           case 'm': i = tm->tm_min; break;
10128           case 's': i = tm->tm_sec; break;
10129           default:  i = 0;
10130         }
10131         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10132     }
10133     return buf;
10134 }
10135
10136 int
10137 CountPlayers (char *p)
10138 {
10139     int n = 0;
10140     while(p = strchr(p, '\n')) p++, n++; // count participants
10141     return n;
10142 }
10143
10144 FILE *
10145 WriteTourneyFile (char *results, FILE *f)
10146 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10147     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10148     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10149         // create a file with tournament description
10150         fprintf(f, "-participants {%s}\n", appData.participants);
10151         fprintf(f, "-seedBase %d\n", appData.seedBase);
10152         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10153         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10154         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10155         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10156         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10157         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10158         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10159         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10160         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10161         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10162         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10163         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10164         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10165         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10166         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10167         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10168         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10169         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10170         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10171         fprintf(f, "-smpCores %d\n", appData.smpCores);
10172         if(searchTime > 0)
10173                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10174         else {
10175                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10176                 fprintf(f, "-tc %s\n", appData.timeControl);
10177                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10178         }
10179         fprintf(f, "-results \"%s\"\n", results);
10180     }
10181     return f;
10182 }
10183
10184 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10185
10186 void
10187 Substitute (char *participants, int expunge)
10188 {
10189     int i, changed, changes=0, nPlayers=0;
10190     char *p, *q, *r, buf[MSG_SIZ];
10191     if(participants == NULL) return;
10192     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10193     r = p = participants; q = appData.participants;
10194     while(*p && *p == *q) {
10195         if(*p == '\n') r = p+1, nPlayers++;
10196         p++; q++;
10197     }
10198     if(*p) { // difference
10199         while(*p && *p++ != '\n');
10200         while(*q && *q++ != '\n');
10201       changed = nPlayers;
10202         changes = 1 + (strcmp(p, q) != 0);
10203     }
10204     if(changes == 1) { // a single engine mnemonic was changed
10205         q = r; while(*q) nPlayers += (*q++ == '\n');
10206         p = buf; while(*r && (*p = *r++) != '\n') p++;
10207         *p = NULLCHAR;
10208         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10209         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10210         if(mnemonic[i]) { // The substitute is valid
10211             FILE *f;
10212             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10213                 flock(fileno(f), LOCK_EX);
10214                 ParseArgsFromFile(f);
10215                 fseek(f, 0, SEEK_SET);
10216                 FREE(appData.participants); appData.participants = participants;
10217                 if(expunge) { // erase results of replaced engine
10218                     int len = strlen(appData.results), w, b, dummy;
10219                     for(i=0; i<len; i++) {
10220                         Pairing(i, nPlayers, &w, &b, &dummy);
10221                         if((w == changed || b == changed) && appData.results[i] == '*') {
10222                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10223                             fclose(f);
10224                             return;
10225                         }
10226                     }
10227                     for(i=0; i<len; i++) {
10228                         Pairing(i, nPlayers, &w, &b, &dummy);
10229                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10230                     }
10231                 }
10232                 WriteTourneyFile(appData.results, f);
10233                 fclose(f); // release lock
10234                 return;
10235             }
10236         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10237     }
10238     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10239     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10240     free(participants);
10241     return;
10242 }
10243
10244 int
10245 CheckPlayers (char *participants)
10246 {
10247         int i;
10248         char buf[MSG_SIZ], *p;
10249         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10250         while(p = strchr(participants, '\n')) {
10251             *p = NULLCHAR;
10252             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10253             if(!mnemonic[i]) {
10254                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10255                 *p = '\n';
10256                 DisplayError(buf, 0);
10257                 return 1;
10258             }
10259             *p = '\n';
10260             participants = p + 1;
10261         }
10262         return 0;
10263 }
10264
10265 int
10266 CreateTourney (char *name)
10267 {
10268         FILE *f;
10269         if(matchMode && strcmp(name, appData.tourneyFile)) {
10270              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10271         }
10272         if(name[0] == NULLCHAR) {
10273             if(appData.participants[0])
10274                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10275             return 0;
10276         }
10277         f = fopen(name, "r");
10278         if(f) { // file exists
10279             ASSIGN(appData.tourneyFile, name);
10280             ParseArgsFromFile(f); // parse it
10281         } else {
10282             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10283             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10284                 DisplayError(_("Not enough participants"), 0);
10285                 return 0;
10286             }
10287             if(CheckPlayers(appData.participants)) return 0;
10288             ASSIGN(appData.tourneyFile, name);
10289             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10290             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10291         }
10292         fclose(f);
10293         appData.noChessProgram = FALSE;
10294         appData.clockMode = TRUE;
10295         SetGNUMode();
10296         return 1;
10297 }
10298
10299 int
10300 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10301 {
10302     char buf[MSG_SIZ], *p, *q;
10303     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10304     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10305     skip = !all && group[0]; // if group requested, we start in skip mode
10306     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10307         p = names; q = buf; header = 0;
10308         while(*p && *p != '\n') *q++ = *p++;
10309         *q = 0;
10310         if(*p == '\n') p++;
10311         if(buf[0] == '#') {
10312             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10313             depth++; // we must be entering a new group
10314             if(all) continue; // suppress printing group headers when complete list requested
10315             header = 1;
10316             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10317         }
10318         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10319         if(engineList[i]) free(engineList[i]);
10320         engineList[i] = strdup(buf);
10321         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10322         if(engineMnemonic[i]) free(engineMnemonic[i]);
10323         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10324             strcat(buf, " (");
10325             sscanf(q + 8, "%s", buf + strlen(buf));
10326             strcat(buf, ")");
10327         }
10328         engineMnemonic[i] = strdup(buf);
10329         i++;
10330     }
10331     engineList[i] = engineMnemonic[i] = NULL;
10332     return i;
10333 }
10334
10335 // following implemented as macro to avoid type limitations
10336 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10337
10338 void
10339 SwapEngines (int n)
10340 {   // swap settings for first engine and other engine (so far only some selected options)
10341     int h;
10342     char *p;
10343     if(n == 0) return;
10344     SWAP(directory, p)
10345     SWAP(chessProgram, p)
10346     SWAP(isUCI, h)
10347     SWAP(hasOwnBookUCI, h)
10348     SWAP(protocolVersion, h)
10349     SWAP(reuse, h)
10350     SWAP(scoreIsAbsolute, h)
10351     SWAP(timeOdds, h)
10352     SWAP(logo, p)
10353     SWAP(pgnName, p)
10354     SWAP(pvSAN, h)
10355     SWAP(engOptions, p)
10356     SWAP(engInitString, p)
10357     SWAP(computerString, p)
10358     SWAP(features, p)
10359     SWAP(fenOverride, p)
10360     SWAP(NPS, h)
10361     SWAP(accumulateTC, h)
10362     SWAP(host, p)
10363 }
10364
10365 int
10366 GetEngineLine (char *s, int n)
10367 {
10368     int i;
10369     char buf[MSG_SIZ];
10370     extern char *icsNames;
10371     if(!s || !*s) return 0;
10372     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10373     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10374     if(!mnemonic[i]) return 0;
10375     if(n == 11) return 1; // just testing if there was a match
10376     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10377     if(n == 1) SwapEngines(n);
10378     ParseArgsFromString(buf);
10379     if(n == 1) SwapEngines(n);
10380     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10381         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10382         ParseArgsFromString(buf);
10383     }
10384     return 1;
10385 }
10386
10387 int
10388 SetPlayer (int player, char *p)
10389 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10390     int i;
10391     char buf[MSG_SIZ], *engineName;
10392     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10393     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10394     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10395     if(mnemonic[i]) {
10396         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10397         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10398         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10399         ParseArgsFromString(buf);
10400     } else { // no engine with this nickname is installed!
10401         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10402         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10403         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10404         ModeHighlight();
10405         DisplayError(buf, 0);
10406         return 0;
10407     }
10408     free(engineName);
10409     return i;
10410 }
10411
10412 char *recentEngines;
10413
10414 void
10415 RecentEngineEvent (int nr)
10416 {
10417     int n;
10418 //    SwapEngines(1); // bump first to second
10419 //    ReplaceEngine(&second, 1); // and load it there
10420     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10421     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10422     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10423         ReplaceEngine(&first, 0);
10424         FloatToFront(&appData.recentEngineList, command[n]);
10425     }
10426 }
10427
10428 int
10429 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10430 {   // determine players from game number
10431     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10432
10433     if(appData.tourneyType == 0) {
10434         roundsPerCycle = (nPlayers - 1) | 1;
10435         pairingsPerRound = nPlayers / 2;
10436     } else if(appData.tourneyType > 0) {
10437         roundsPerCycle = nPlayers - appData.tourneyType;
10438         pairingsPerRound = appData.tourneyType;
10439     }
10440     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10441     gamesPerCycle = gamesPerRound * roundsPerCycle;
10442     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10443     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10444     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10445     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10446     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10447     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10448
10449     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10450     if(appData.roundSync) *syncInterval = gamesPerRound;
10451
10452     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10453
10454     if(appData.tourneyType == 0) {
10455         if(curPairing == (nPlayers-1)/2 ) {
10456             *whitePlayer = curRound;
10457             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10458         } else {
10459             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10460             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10461             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10462             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10463         }
10464     } else if(appData.tourneyType > 1) {
10465         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10466         *whitePlayer = curRound + appData.tourneyType;
10467     } else if(appData.tourneyType > 0) {
10468         *whitePlayer = curPairing;
10469         *blackPlayer = curRound + appData.tourneyType;
10470     }
10471
10472     // take care of white/black alternation per round.
10473     // For cycles and games this is already taken care of by default, derived from matchGame!
10474     return curRound & 1;
10475 }
10476
10477 int
10478 NextTourneyGame (int nr, int *swapColors)
10479 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10480     char *p, *q;
10481     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10482     FILE *tf;
10483     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10484     tf = fopen(appData.tourneyFile, "r");
10485     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10486     ParseArgsFromFile(tf); fclose(tf);
10487     InitTimeControls(); // TC might be altered from tourney file
10488
10489     nPlayers = CountPlayers(appData.participants); // count participants
10490     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10491     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10492
10493     if(syncInterval) {
10494         p = q = appData.results;
10495         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10496         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10497             DisplayMessage(_("Waiting for other game(s)"),"");
10498             waitingForGame = TRUE;
10499             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10500             return 0;
10501         }
10502         waitingForGame = FALSE;
10503     }
10504
10505     if(appData.tourneyType < 0) {
10506         if(nr>=0 && !pairingReceived) {
10507             char buf[1<<16];
10508             if(pairing.pr == NoProc) {
10509                 if(!appData.pairingEngine[0]) {
10510                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10511                     return 0;
10512                 }
10513                 StartChessProgram(&pairing); // starts the pairing engine
10514             }
10515             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10516             SendToProgram(buf, &pairing);
10517             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10518             SendToProgram(buf, &pairing);
10519             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10520         }
10521         pairingReceived = 0;                              // ... so we continue here
10522         *swapColors = 0;
10523         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10524         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10525         matchGame = 1; roundNr = nr / syncInterval + 1;
10526     }
10527
10528     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10529
10530     // redefine engines, engine dir, etc.
10531     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10532     if(first.pr == NoProc) {
10533       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10534       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10535     }
10536     if(second.pr == NoProc) {
10537       SwapEngines(1);
10538       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10539       SwapEngines(1);         // and make that valid for second engine by swapping
10540       InitEngine(&second, 1);
10541     }
10542     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10543     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10544     return OK;
10545 }
10546
10547 void
10548 NextMatchGame ()
10549 {   // performs game initialization that does not invoke engines, and then tries to start the game
10550     int res, firstWhite, swapColors = 0;
10551     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10552     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
10553         char buf[MSG_SIZ];
10554         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10555         if(strcmp(buf, currentDebugFile)) { // name has changed
10556             FILE *f = fopen(buf, "w");
10557             if(f) { // if opening the new file failed, just keep using the old one
10558                 ASSIGN(currentDebugFile, buf);
10559                 fclose(debugFP);
10560                 debugFP = f;
10561             }
10562             if(appData.serverFileName) {
10563                 if(serverFP) fclose(serverFP);
10564                 serverFP = fopen(appData.serverFileName, "w");
10565                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10566                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10567             }
10568         }
10569     }
10570     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10571     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10572     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10573     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10574     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10575     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10576     Reset(FALSE, first.pr != NoProc);
10577     res = LoadGameOrPosition(matchGame); // setup game
10578     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10579     if(!res) return; // abort when bad game/pos file
10580     TwoMachinesEvent();
10581 }
10582
10583 void
10584 UserAdjudicationEvent (int result)
10585 {
10586     ChessMove gameResult = GameIsDrawn;
10587
10588     if( result > 0 ) {
10589         gameResult = WhiteWins;
10590     }
10591     else if( result < 0 ) {
10592         gameResult = BlackWins;
10593     }
10594
10595     if( gameMode == TwoMachinesPlay ) {
10596         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10597     }
10598 }
10599
10600
10601 // [HGM] save: calculate checksum of game to make games easily identifiable
10602 int
10603 StringCheckSum (char *s)
10604 {
10605         int i = 0;
10606         if(s==NULL) return 0;
10607         while(*s) i = i*259 + *s++;
10608         return i;
10609 }
10610
10611 int
10612 GameCheckSum ()
10613 {
10614         int i, sum=0;
10615         for(i=backwardMostMove; i<forwardMostMove; i++) {
10616                 sum += pvInfoList[i].depth;
10617                 sum += StringCheckSum(parseList[i]);
10618                 sum += StringCheckSum(commentList[i]);
10619                 sum *= 261;
10620         }
10621         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10622         return sum + StringCheckSum(commentList[i]);
10623 } // end of save patch
10624
10625 void
10626 GameEnds (ChessMove result, char *resultDetails, int whosays)
10627 {
10628     GameMode nextGameMode;
10629     int isIcsGame;
10630     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10631
10632     if(endingGame) return; /* [HGM] crash: forbid recursion */
10633     endingGame = 1;
10634     if(twoBoards) { // [HGM] dual: switch back to one board
10635         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10636         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10637     }
10638     if (appData.debugMode) {
10639       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10640               result, resultDetails ? resultDetails : "(null)", whosays);
10641     }
10642
10643     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10644
10645     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10646
10647     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10648         /* If we are playing on ICS, the server decides when the
10649            game is over, but the engine can offer to draw, claim
10650            a draw, or resign.
10651          */
10652 #if ZIPPY
10653         if (appData.zippyPlay && first.initDone) {
10654             if (result == GameIsDrawn) {
10655                 /* In case draw still needs to be claimed */
10656                 SendToICS(ics_prefix);
10657                 SendToICS("draw\n");
10658             } else if (StrCaseStr(resultDetails, "resign")) {
10659                 SendToICS(ics_prefix);
10660                 SendToICS("resign\n");
10661             }
10662         }
10663 #endif
10664         endingGame = 0; /* [HGM] crash */
10665         return;
10666     }
10667
10668     /* If we're loading the game from a file, stop */
10669     if (whosays == GE_FILE) {
10670       (void) StopLoadGameTimer();
10671       gameFileFP = NULL;
10672     }
10673
10674     /* Cancel draw offers */
10675     first.offeredDraw = second.offeredDraw = 0;
10676
10677     /* If this is an ICS game, only ICS can really say it's done;
10678        if not, anyone can. */
10679     isIcsGame = (gameMode == IcsPlayingWhite ||
10680                  gameMode == IcsPlayingBlack ||
10681                  gameMode == IcsObserving    ||
10682                  gameMode == IcsExamining);
10683
10684     if (!isIcsGame || whosays == GE_ICS) {
10685         /* OK -- not an ICS game, or ICS said it was done */
10686         StopClocks();
10687         if (!isIcsGame && !appData.noChessProgram)
10688           SetUserThinkingEnables();
10689
10690         /* [HGM] if a machine claims the game end we verify this claim */
10691         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10692             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10693                 char claimer;
10694                 ChessMove trueResult = (ChessMove) -1;
10695
10696                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10697                                             first.twoMachinesColor[0] :
10698                                             second.twoMachinesColor[0] ;
10699
10700                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10701                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10702                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10703                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10704                 } else
10705                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10706                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10707                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10708                 } else
10709                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10710                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10711                 }
10712
10713                 // now verify win claims, but not in drop games, as we don't understand those yet
10714                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10715                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10716                     (result == WhiteWins && claimer == 'w' ||
10717                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10718                       if (appData.debugMode) {
10719                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10720                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10721                       }
10722                       if(result != trueResult) {
10723                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10724                               result = claimer == 'w' ? BlackWins : WhiteWins;
10725                               resultDetails = buf;
10726                       }
10727                 } else
10728                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10729                     && (forwardMostMove <= backwardMostMove ||
10730                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10731                         (claimer=='b')==(forwardMostMove&1))
10732                                                                                   ) {
10733                       /* [HGM] verify: draws that were not flagged are false claims */
10734                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10735                       result = claimer == 'w' ? BlackWins : WhiteWins;
10736                       resultDetails = buf;
10737                 }
10738                 /* (Claiming a loss is accepted no questions asked!) */
10739             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10740                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10741                 result = GameUnfinished;
10742                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10743             }
10744             /* [HGM] bare: don't allow bare King to win */
10745             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10746                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10747                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10748                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10749                && result != GameIsDrawn)
10750             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10751                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10752                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10753                         if(p >= 0 && p <= (int)WhiteKing) k++;
10754                 }
10755                 if (appData.debugMode) {
10756                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10757                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10758                 }
10759                 if(k <= 1) {
10760                         result = GameIsDrawn;
10761                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10762                         resultDetails = buf;
10763                 }
10764             }
10765         }
10766
10767
10768         if(serverMoves != NULL && !loadFlag) { char c = '=';
10769             if(result==WhiteWins) c = '+';
10770             if(result==BlackWins) c = '-';
10771             if(resultDetails != NULL)
10772                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10773         }
10774         if (resultDetails != NULL) {
10775             gameInfo.result = result;
10776             gameInfo.resultDetails = StrSave(resultDetails);
10777
10778             /* display last move only if game was not loaded from file */
10779             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10780                 DisplayMove(currentMove - 1);
10781
10782             if (forwardMostMove != 0) {
10783                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10784                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10785                                                                 ) {
10786                     if (*appData.saveGameFile != NULLCHAR) {
10787                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10788                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10789                         else
10790                         SaveGameToFile(appData.saveGameFile, TRUE);
10791                     } else if (appData.autoSaveGames) {
10792                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10793                     }
10794                     if (*appData.savePositionFile != NULLCHAR) {
10795                         SavePositionToFile(appData.savePositionFile);
10796                     }
10797                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10798                 }
10799             }
10800
10801             /* Tell program how game ended in case it is learning */
10802             /* [HGM] Moved this to after saving the PGN, just in case */
10803             /* engine died and we got here through time loss. In that */
10804             /* case we will get a fatal error writing the pipe, which */
10805             /* would otherwise lose us the PGN.                       */
10806             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10807             /* output during GameEnds should never be fatal anymore   */
10808             if (gameMode == MachinePlaysWhite ||
10809                 gameMode == MachinePlaysBlack ||
10810                 gameMode == TwoMachinesPlay ||
10811                 gameMode == IcsPlayingWhite ||
10812                 gameMode == IcsPlayingBlack ||
10813                 gameMode == BeginningOfGame) {
10814                 char buf[MSG_SIZ];
10815                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10816                         resultDetails);
10817                 if (first.pr != NoProc) {
10818                     SendToProgram(buf, &first);
10819                 }
10820                 if (second.pr != NoProc &&
10821                     gameMode == TwoMachinesPlay) {
10822                     SendToProgram(buf, &second);
10823                 }
10824             }
10825         }
10826
10827         if (appData.icsActive) {
10828             if (appData.quietPlay &&
10829                 (gameMode == IcsPlayingWhite ||
10830                  gameMode == IcsPlayingBlack)) {
10831                 SendToICS(ics_prefix);
10832                 SendToICS("set shout 1\n");
10833             }
10834             nextGameMode = IcsIdle;
10835             ics_user_moved = FALSE;
10836             /* clean up premove.  It's ugly when the game has ended and the
10837              * premove highlights are still on the board.
10838              */
10839             if (gotPremove) {
10840               gotPremove = FALSE;
10841               ClearPremoveHighlights();
10842               DrawPosition(FALSE, boards[currentMove]);
10843             }
10844             if (whosays == GE_ICS) {
10845                 switch (result) {
10846                 case WhiteWins:
10847                     if (gameMode == IcsPlayingWhite)
10848                         PlayIcsWinSound();
10849                     else if(gameMode == IcsPlayingBlack)
10850                         PlayIcsLossSound();
10851                     break;
10852                 case BlackWins:
10853                     if (gameMode == IcsPlayingBlack)
10854                         PlayIcsWinSound();
10855                     else if(gameMode == IcsPlayingWhite)
10856                         PlayIcsLossSound();
10857                     break;
10858                 case GameIsDrawn:
10859                     PlayIcsDrawSound();
10860                     break;
10861                 default:
10862                     PlayIcsUnfinishedSound();
10863                 }
10864             }
10865             if(appData.quitNext) { ExitEvent(0); return; }
10866         } else if (gameMode == EditGame ||
10867                    gameMode == PlayFromGameFile ||
10868                    gameMode == AnalyzeMode ||
10869                    gameMode == AnalyzeFile) {
10870             nextGameMode = gameMode;
10871         } else {
10872             nextGameMode = EndOfGame;
10873         }
10874         pausing = FALSE;
10875         ModeHighlight();
10876     } else {
10877         nextGameMode = gameMode;
10878     }
10879
10880     if (appData.noChessProgram) {
10881         gameMode = nextGameMode;
10882         ModeHighlight();
10883         endingGame = 0; /* [HGM] crash */
10884         return;
10885     }
10886
10887     if (first.reuse) {
10888         /* Put first chess program into idle state */
10889         if (first.pr != NoProc &&
10890             (gameMode == MachinePlaysWhite ||
10891              gameMode == MachinePlaysBlack ||
10892              gameMode == TwoMachinesPlay ||
10893              gameMode == IcsPlayingWhite ||
10894              gameMode == IcsPlayingBlack ||
10895              gameMode == BeginningOfGame)) {
10896             SendToProgram("force\n", &first);
10897             if (first.usePing) {
10898               char buf[MSG_SIZ];
10899               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10900               SendToProgram(buf, &first);
10901             }
10902         }
10903     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10904         /* Kill off first chess program */
10905         if (first.isr != NULL)
10906           RemoveInputSource(first.isr);
10907         first.isr = NULL;
10908
10909         if (first.pr != NoProc) {
10910             ExitAnalyzeMode();
10911             DoSleep( appData.delayBeforeQuit );
10912             SendToProgram("quit\n", &first);
10913             DoSleep( appData.delayAfterQuit );
10914             DestroyChildProcess(first.pr, first.useSigterm);
10915             first.reload = TRUE;
10916         }
10917         first.pr = NoProc;
10918     }
10919     if (second.reuse) {
10920         /* Put second chess program into idle state */
10921         if (second.pr != NoProc &&
10922             gameMode == TwoMachinesPlay) {
10923             SendToProgram("force\n", &second);
10924             if (second.usePing) {
10925               char buf[MSG_SIZ];
10926               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10927               SendToProgram(buf, &second);
10928             }
10929         }
10930     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10931         /* Kill off second chess program */
10932         if (second.isr != NULL)
10933           RemoveInputSource(second.isr);
10934         second.isr = NULL;
10935
10936         if (second.pr != NoProc) {
10937             DoSleep( appData.delayBeforeQuit );
10938             SendToProgram("quit\n", &second);
10939             DoSleep( appData.delayAfterQuit );
10940             DestroyChildProcess(second.pr, second.useSigterm);
10941             second.reload = TRUE;
10942         }
10943         second.pr = NoProc;
10944     }
10945
10946     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10947         char resChar = '=';
10948         switch (result) {
10949         case WhiteWins:
10950           resChar = '+';
10951           if (first.twoMachinesColor[0] == 'w') {
10952             first.matchWins++;
10953           } else {
10954             second.matchWins++;
10955           }
10956           break;
10957         case BlackWins:
10958           resChar = '-';
10959           if (first.twoMachinesColor[0] == 'b') {
10960             first.matchWins++;
10961           } else {
10962             second.matchWins++;
10963           }
10964           break;
10965         case GameUnfinished:
10966           resChar = ' ';
10967         default:
10968           break;
10969         }
10970
10971         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10972         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10973             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10974             ReserveGame(nextGame, resChar); // sets nextGame
10975             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10976             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10977         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10978
10979         if (nextGame <= appData.matchGames && !abortMatch) {
10980             gameMode = nextGameMode;
10981             matchGame = nextGame; // this will be overruled in tourney mode!
10982             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10983             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10984             endingGame = 0; /* [HGM] crash */
10985             return;
10986         } else {
10987             gameMode = nextGameMode;
10988             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10989                      first.tidy, second.tidy,
10990                      first.matchWins, second.matchWins,
10991                      appData.matchGames - (first.matchWins + second.matchWins));
10992             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10993             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10994             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10995             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10996                 first.twoMachinesColor = "black\n";
10997                 second.twoMachinesColor = "white\n";
10998             } else {
10999                 first.twoMachinesColor = "white\n";
11000                 second.twoMachinesColor = "black\n";
11001             }
11002         }
11003     }
11004     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11005         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11006       ExitAnalyzeMode();
11007     gameMode = nextGameMode;
11008     ModeHighlight();
11009     endingGame = 0;  /* [HGM] crash */
11010     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11011         if(matchMode == TRUE) { // match through command line: exit with or without popup
11012             if(ranking) {
11013                 ToNrEvent(forwardMostMove);
11014                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11015                 else ExitEvent(0);
11016             } else DisplayFatalError(buf, 0, 0);
11017         } else { // match through menu; just stop, with or without popup
11018             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11019             ModeHighlight();
11020             if(ranking){
11021                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11022             } else DisplayNote(buf);
11023       }
11024       if(ranking) free(ranking);
11025     }
11026 }
11027
11028 /* Assumes program was just initialized (initString sent).
11029    Leaves program in force mode. */
11030 void
11031 FeedMovesToProgram (ChessProgramState *cps, int upto)
11032 {
11033     int i;
11034
11035     if (appData.debugMode)
11036       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11037               startedFromSetupPosition ? "position and " : "",
11038               backwardMostMove, upto, cps->which);
11039     if(currentlyInitializedVariant != gameInfo.variant) {
11040       char buf[MSG_SIZ];
11041         // [HGM] variantswitch: make engine aware of new variant
11042         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11043                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11044         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11045         SendToProgram(buf, cps);
11046         currentlyInitializedVariant = gameInfo.variant;
11047     }
11048     SendToProgram("force\n", cps);
11049     if (startedFromSetupPosition) {
11050         SendBoard(cps, backwardMostMove);
11051     if (appData.debugMode) {
11052         fprintf(debugFP, "feedMoves\n");
11053     }
11054     }
11055     for (i = backwardMostMove; i < upto; i++) {
11056         SendMoveToProgram(i, cps);
11057     }
11058 }
11059
11060
11061 int
11062 ResurrectChessProgram ()
11063 {
11064      /* The chess program may have exited.
11065         If so, restart it and feed it all the moves made so far. */
11066     static int doInit = 0;
11067
11068     if (appData.noChessProgram) return 1;
11069
11070     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11071         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11072         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11073         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11074     } else {
11075         if (first.pr != NoProc) return 1;
11076         StartChessProgram(&first);
11077     }
11078     InitChessProgram(&first, FALSE);
11079     FeedMovesToProgram(&first, currentMove);
11080
11081     if (!first.sendTime) {
11082         /* can't tell gnuchess what its clock should read,
11083            so we bow to its notion. */
11084         ResetClocks();
11085         timeRemaining[0][currentMove] = whiteTimeRemaining;
11086         timeRemaining[1][currentMove] = blackTimeRemaining;
11087     }
11088
11089     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11090                 appData.icsEngineAnalyze) && first.analysisSupport) {
11091       SendToProgram("analyze\n", &first);
11092       first.analyzing = TRUE;
11093     }
11094     return 1;
11095 }
11096
11097 /*
11098  * Button procedures
11099  */
11100 void
11101 Reset (int redraw, int init)
11102 {
11103     int i;
11104
11105     if (appData.debugMode) {
11106         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11107                 redraw, init, gameMode);
11108     }
11109     CleanupTail(); // [HGM] vari: delete any stored variations
11110     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11111     pausing = pauseExamInvalid = FALSE;
11112     startedFromSetupPosition = blackPlaysFirst = FALSE;
11113     firstMove = TRUE;
11114     whiteFlag = blackFlag = FALSE;
11115     userOfferedDraw = FALSE;
11116     hintRequested = bookRequested = FALSE;
11117     first.maybeThinking = FALSE;
11118     second.maybeThinking = FALSE;
11119     first.bookSuspend = FALSE; // [HGM] book
11120     second.bookSuspend = FALSE;
11121     thinkOutput[0] = NULLCHAR;
11122     lastHint[0] = NULLCHAR;
11123     ClearGameInfo(&gameInfo);
11124     gameInfo.variant = StringToVariant(appData.variant);
11125     ics_user_moved = ics_clock_paused = FALSE;
11126     ics_getting_history = H_FALSE;
11127     ics_gamenum = -1;
11128     white_holding[0] = black_holding[0] = NULLCHAR;
11129     ClearProgramStats();
11130     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11131
11132     ResetFrontEnd();
11133     ClearHighlights();
11134     flipView = appData.flipView;
11135     ClearPremoveHighlights();
11136     gotPremove = FALSE;
11137     alarmSounded = FALSE;
11138
11139     GameEnds(EndOfFile, NULL, GE_PLAYER);
11140     if(appData.serverMovesName != NULL) {
11141         /* [HGM] prepare to make moves file for broadcasting */
11142         clock_t t = clock();
11143         if(serverMoves != NULL) fclose(serverMoves);
11144         serverMoves = fopen(appData.serverMovesName, "r");
11145         if(serverMoves != NULL) {
11146             fclose(serverMoves);
11147             /* delay 15 sec before overwriting, so all clients can see end */
11148             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11149         }
11150         serverMoves = fopen(appData.serverMovesName, "w");
11151     }
11152
11153     ExitAnalyzeMode();
11154     gameMode = BeginningOfGame;
11155     ModeHighlight();
11156     if(appData.icsActive) gameInfo.variant = VariantNormal;
11157     currentMove = forwardMostMove = backwardMostMove = 0;
11158     MarkTargetSquares(1);
11159     InitPosition(redraw);
11160     for (i = 0; i < MAX_MOVES; i++) {
11161         if (commentList[i] != NULL) {
11162             free(commentList[i]);
11163             commentList[i] = NULL;
11164         }
11165     }
11166     ResetClocks();
11167     timeRemaining[0][0] = whiteTimeRemaining;
11168     timeRemaining[1][0] = blackTimeRemaining;
11169
11170     if (first.pr == NoProc) {
11171         StartChessProgram(&first);
11172     }
11173     if (init) {
11174             InitChessProgram(&first, startedFromSetupPosition);
11175     }
11176     DisplayTitle("");
11177     DisplayMessage("", "");
11178     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11179     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11180     ClearMap();        // [HGM] exclude: invalidate map
11181 }
11182
11183 void
11184 AutoPlayGameLoop ()
11185 {
11186     for (;;) {
11187         if (!AutoPlayOneMove())
11188           return;
11189         if (matchMode || appData.timeDelay == 0)
11190           continue;
11191         if (appData.timeDelay < 0)
11192           return;
11193         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11194         break;
11195     }
11196 }
11197
11198 void
11199 AnalyzeNextGame()
11200 {
11201     ReloadGame(1); // next game
11202 }
11203
11204 int
11205 AutoPlayOneMove ()
11206 {
11207     int fromX, fromY, toX, toY;
11208
11209     if (appData.debugMode) {
11210       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11211     }
11212
11213     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11214       return FALSE;
11215
11216     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11217       pvInfoList[currentMove].depth = programStats.depth;
11218       pvInfoList[currentMove].score = programStats.score;
11219       pvInfoList[currentMove].time  = 0;
11220       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11221       else { // append analysis of final position as comment
11222         char buf[MSG_SIZ];
11223         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11224         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11225       }
11226       programStats.depth = 0;
11227     }
11228
11229     if (currentMove >= forwardMostMove) {
11230       if(gameMode == AnalyzeFile) {
11231           if(appData.loadGameIndex == -1) {
11232             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11233           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11234           } else {
11235           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11236         }
11237       }
11238 //      gameMode = EndOfGame;
11239 //      ModeHighlight();
11240
11241       /* [AS] Clear current move marker at the end of a game */
11242       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11243
11244       return FALSE;
11245     }
11246
11247     toX = moveList[currentMove][2] - AAA;
11248     toY = moveList[currentMove][3] - ONE;
11249
11250     if (moveList[currentMove][1] == '@') {
11251         if (appData.highlightLastMove) {
11252             SetHighlights(-1, -1, toX, toY);
11253         }
11254     } else {
11255         fromX = moveList[currentMove][0] - AAA;
11256         fromY = moveList[currentMove][1] - ONE;
11257
11258         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11259
11260         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11261
11262         if (appData.highlightLastMove) {
11263             SetHighlights(fromX, fromY, toX, toY);
11264         }
11265     }
11266     DisplayMove(currentMove);
11267     SendMoveToProgram(currentMove++, &first);
11268     DisplayBothClocks();
11269     DrawPosition(FALSE, boards[currentMove]);
11270     // [HGM] PV info: always display, routine tests if empty
11271     DisplayComment(currentMove - 1, commentList[currentMove]);
11272     return TRUE;
11273 }
11274
11275
11276 int
11277 LoadGameOneMove (ChessMove readAhead)
11278 {
11279     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11280     char promoChar = NULLCHAR;
11281     ChessMove moveType;
11282     char move[MSG_SIZ];
11283     char *p, *q;
11284
11285     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11286         gameMode != AnalyzeMode && gameMode != Training) {
11287         gameFileFP = NULL;
11288         return FALSE;
11289     }
11290
11291     yyboardindex = forwardMostMove;
11292     if (readAhead != EndOfFile) {
11293       moveType = readAhead;
11294     } else {
11295       if (gameFileFP == NULL)
11296           return FALSE;
11297       moveType = (ChessMove) Myylex();
11298     }
11299
11300     done = FALSE;
11301     switch (moveType) {
11302       case Comment:
11303         if (appData.debugMode)
11304           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11305         p = yy_text;
11306
11307         /* append the comment but don't display it */
11308         AppendComment(currentMove, p, FALSE);
11309         return TRUE;
11310
11311       case WhiteCapturesEnPassant:
11312       case BlackCapturesEnPassant:
11313       case WhitePromotion:
11314       case BlackPromotion:
11315       case WhiteNonPromotion:
11316       case BlackNonPromotion:
11317       case NormalMove:
11318       case WhiteKingSideCastle:
11319       case WhiteQueenSideCastle:
11320       case BlackKingSideCastle:
11321       case BlackQueenSideCastle:
11322       case WhiteKingSideCastleWild:
11323       case WhiteQueenSideCastleWild:
11324       case BlackKingSideCastleWild:
11325       case BlackQueenSideCastleWild:
11326       /* PUSH Fabien */
11327       case WhiteHSideCastleFR:
11328       case WhiteASideCastleFR:
11329       case BlackHSideCastleFR:
11330       case BlackASideCastleFR:
11331       /* POP Fabien */
11332         if (appData.debugMode)
11333           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11334         fromX = currentMoveString[0] - AAA;
11335         fromY = currentMoveString[1] - ONE;
11336         toX = currentMoveString[2] - AAA;
11337         toY = currentMoveString[3] - ONE;
11338         promoChar = currentMoveString[4];
11339         break;
11340
11341       case WhiteDrop:
11342       case BlackDrop:
11343         if (appData.debugMode)
11344           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11345         fromX = moveType == WhiteDrop ?
11346           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11347         (int) CharToPiece(ToLower(currentMoveString[0]));
11348         fromY = DROP_RANK;
11349         toX = currentMoveString[2] - AAA;
11350         toY = currentMoveString[3] - ONE;
11351         break;
11352
11353       case WhiteWins:
11354       case BlackWins:
11355       case GameIsDrawn:
11356       case GameUnfinished:
11357         if (appData.debugMode)
11358           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11359         p = strchr(yy_text, '{');
11360         if (p == NULL) p = strchr(yy_text, '(');
11361         if (p == NULL) {
11362             p = yy_text;
11363             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11364         } else {
11365             q = strchr(p, *p == '{' ? '}' : ')');
11366             if (q != NULL) *q = NULLCHAR;
11367             p++;
11368         }
11369         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11370         GameEnds(moveType, p, GE_FILE);
11371         done = TRUE;
11372         if (cmailMsgLoaded) {
11373             ClearHighlights();
11374             flipView = WhiteOnMove(currentMove);
11375             if (moveType == GameUnfinished) flipView = !flipView;
11376             if (appData.debugMode)
11377               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11378         }
11379         break;
11380
11381       case EndOfFile:
11382         if (appData.debugMode)
11383           fprintf(debugFP, "Parser hit end of file\n");
11384         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11385           case MT_NONE:
11386           case MT_CHECK:
11387             break;
11388           case MT_CHECKMATE:
11389           case MT_STAINMATE:
11390             if (WhiteOnMove(currentMove)) {
11391                 GameEnds(BlackWins, "Black mates", GE_FILE);
11392             } else {
11393                 GameEnds(WhiteWins, "White mates", GE_FILE);
11394             }
11395             break;
11396           case MT_STALEMATE:
11397             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11398             break;
11399         }
11400         done = TRUE;
11401         break;
11402
11403       case MoveNumberOne:
11404         if (lastLoadGameStart == GNUChessGame) {
11405             /* GNUChessGames have numbers, but they aren't move numbers */
11406             if (appData.debugMode)
11407               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11408                       yy_text, (int) moveType);
11409             return LoadGameOneMove(EndOfFile); /* tail recursion */
11410         }
11411         /* else fall thru */
11412
11413       case XBoardGame:
11414       case GNUChessGame:
11415       case PGNTag:
11416         /* Reached start of next game in file */
11417         if (appData.debugMode)
11418           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11419         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11420           case MT_NONE:
11421           case MT_CHECK:
11422             break;
11423           case MT_CHECKMATE:
11424           case MT_STAINMATE:
11425             if (WhiteOnMove(currentMove)) {
11426                 GameEnds(BlackWins, "Black mates", GE_FILE);
11427             } else {
11428                 GameEnds(WhiteWins, "White mates", GE_FILE);
11429             }
11430             break;
11431           case MT_STALEMATE:
11432             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11433             break;
11434         }
11435         done = TRUE;
11436         break;
11437
11438       case PositionDiagram:     /* should not happen; ignore */
11439       case ElapsedTime:         /* ignore */
11440       case NAG:                 /* ignore */
11441         if (appData.debugMode)
11442           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11443                   yy_text, (int) moveType);
11444         return LoadGameOneMove(EndOfFile); /* tail recursion */
11445
11446       case IllegalMove:
11447         if (appData.testLegality) {
11448             if (appData.debugMode)
11449               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11450             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11451                     (forwardMostMove / 2) + 1,
11452                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11453             DisplayError(move, 0);
11454             done = TRUE;
11455         } else {
11456             if (appData.debugMode)
11457               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11458                       yy_text, currentMoveString);
11459             fromX = currentMoveString[0] - AAA;
11460             fromY = currentMoveString[1] - ONE;
11461             toX = currentMoveString[2] - AAA;
11462             toY = currentMoveString[3] - ONE;
11463             promoChar = currentMoveString[4];
11464         }
11465         break;
11466
11467       case AmbiguousMove:
11468         if (appData.debugMode)
11469           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11470         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11471                 (forwardMostMove / 2) + 1,
11472                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11473         DisplayError(move, 0);
11474         done = TRUE;
11475         break;
11476
11477       default:
11478       case ImpossibleMove:
11479         if (appData.debugMode)
11480           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11481         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11482                 (forwardMostMove / 2) + 1,
11483                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11484         DisplayError(move, 0);
11485         done = TRUE;
11486         break;
11487     }
11488
11489     if (done) {
11490         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11491             DrawPosition(FALSE, boards[currentMove]);
11492             DisplayBothClocks();
11493             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11494               DisplayComment(currentMove - 1, commentList[currentMove]);
11495         }
11496         (void) StopLoadGameTimer();
11497         gameFileFP = NULL;
11498         cmailOldMove = forwardMostMove;
11499         return FALSE;
11500     } else {
11501         /* currentMoveString is set as a side-effect of yylex */
11502
11503         thinkOutput[0] = NULLCHAR;
11504         MakeMove(fromX, fromY, toX, toY, promoChar);
11505         currentMove = forwardMostMove;
11506         return TRUE;
11507     }
11508 }
11509
11510 /* Load the nth game from the given file */
11511 int
11512 LoadGameFromFile (char *filename, int n, char *title, int useList)
11513 {
11514     FILE *f;
11515     char buf[MSG_SIZ];
11516
11517     if (strcmp(filename, "-") == 0) {
11518         f = stdin;
11519         title = "stdin";
11520     } else {
11521         f = fopen(filename, "rb");
11522         if (f == NULL) {
11523           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11524             DisplayError(buf, errno);
11525             return FALSE;
11526         }
11527     }
11528     if (fseek(f, 0, 0) == -1) {
11529         /* f is not seekable; probably a pipe */
11530         useList = FALSE;
11531     }
11532     if (useList && n == 0) {
11533         int error = GameListBuild(f);
11534         if (error) {
11535             DisplayError(_("Cannot build game list"), error);
11536         } else if (!ListEmpty(&gameList) &&
11537                    ((ListGame *) gameList.tailPred)->number > 1) {
11538             GameListPopUp(f, title);
11539             return TRUE;
11540         }
11541         GameListDestroy();
11542         n = 1;
11543     }
11544     if (n == 0) n = 1;
11545     return LoadGame(f, n, title, FALSE);
11546 }
11547
11548
11549 void
11550 MakeRegisteredMove ()
11551 {
11552     int fromX, fromY, toX, toY;
11553     char promoChar;
11554     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11555         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11556           case CMAIL_MOVE:
11557           case CMAIL_DRAW:
11558             if (appData.debugMode)
11559               fprintf(debugFP, "Restoring %s for game %d\n",
11560                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11561
11562             thinkOutput[0] = NULLCHAR;
11563             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11564             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11565             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11566             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11567             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11568             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11569             MakeMove(fromX, fromY, toX, toY, promoChar);
11570             ShowMove(fromX, fromY, toX, toY);
11571
11572             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11573               case MT_NONE:
11574               case MT_CHECK:
11575                 break;
11576
11577               case MT_CHECKMATE:
11578               case MT_STAINMATE:
11579                 if (WhiteOnMove(currentMove)) {
11580                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11581                 } else {
11582                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11583                 }
11584                 break;
11585
11586               case MT_STALEMATE:
11587                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11588                 break;
11589             }
11590
11591             break;
11592
11593           case CMAIL_RESIGN:
11594             if (WhiteOnMove(currentMove)) {
11595                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11596             } else {
11597                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11598             }
11599             break;
11600
11601           case CMAIL_ACCEPT:
11602             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11603             break;
11604
11605           default:
11606             break;
11607         }
11608     }
11609
11610     return;
11611 }
11612
11613 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11614 int
11615 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11616 {
11617     int retVal;
11618
11619     if (gameNumber > nCmailGames) {
11620         DisplayError(_("No more games in this message"), 0);
11621         return FALSE;
11622     }
11623     if (f == lastLoadGameFP) {
11624         int offset = gameNumber - lastLoadGameNumber;
11625         if (offset == 0) {
11626             cmailMsg[0] = NULLCHAR;
11627             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11628                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11629                 nCmailMovesRegistered--;
11630             }
11631             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11632             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11633                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11634             }
11635         } else {
11636             if (! RegisterMove()) return FALSE;
11637         }
11638     }
11639
11640     retVal = LoadGame(f, gameNumber, title, useList);
11641
11642     /* Make move registered during previous look at this game, if any */
11643     MakeRegisteredMove();
11644
11645     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11646         commentList[currentMove]
11647           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11648         DisplayComment(currentMove - 1, commentList[currentMove]);
11649     }
11650
11651     return retVal;
11652 }
11653
11654 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11655 int
11656 ReloadGame (int offset)
11657 {
11658     int gameNumber = lastLoadGameNumber + offset;
11659     if (lastLoadGameFP == NULL) {
11660         DisplayError(_("No game has been loaded yet"), 0);
11661         return FALSE;
11662     }
11663     if (gameNumber <= 0) {
11664         DisplayError(_("Can't back up any further"), 0);
11665         return FALSE;
11666     }
11667     if (cmailMsgLoaded) {
11668         return CmailLoadGame(lastLoadGameFP, gameNumber,
11669                              lastLoadGameTitle, lastLoadGameUseList);
11670     } else {
11671         return LoadGame(lastLoadGameFP, gameNumber,
11672                         lastLoadGameTitle, lastLoadGameUseList);
11673     }
11674 }
11675
11676 int keys[EmptySquare+1];
11677
11678 int
11679 PositionMatches (Board b1, Board b2)
11680 {
11681     int r, f, sum=0;
11682     switch(appData.searchMode) {
11683         case 1: return CompareWithRights(b1, b2);
11684         case 2:
11685             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11686                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11687             }
11688             return TRUE;
11689         case 3:
11690             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11691               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11692                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11693             }
11694             return sum==0;
11695         case 4:
11696             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11697                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11698             }
11699             return sum==0;
11700     }
11701     return TRUE;
11702 }
11703
11704 #define Q_PROMO  4
11705 #define Q_EP     3
11706 #define Q_BCASTL 2
11707 #define Q_WCASTL 1
11708
11709 int pieceList[256], quickBoard[256];
11710 ChessSquare pieceType[256] = { EmptySquare };
11711 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11712 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11713 int soughtTotal, turn;
11714 Boolean epOK, flipSearch;
11715
11716 typedef struct {
11717     unsigned char piece, to;
11718 } Move;
11719
11720 #define DSIZE (250000)
11721
11722 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11723 Move *moveDatabase = initialSpace;
11724 unsigned int movePtr, dataSize = DSIZE;
11725
11726 int
11727 MakePieceList (Board board, int *counts)
11728 {
11729     int r, f, n=Q_PROMO, total=0;
11730     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11731     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11732         int sq = f + (r<<4);
11733         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11734             quickBoard[sq] = ++n;
11735             pieceList[n] = sq;
11736             pieceType[n] = board[r][f];
11737             counts[board[r][f]]++;
11738             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11739             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11740             total++;
11741         }
11742     }
11743     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11744     return total;
11745 }
11746
11747 void
11748 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11749 {
11750     int sq = fromX + (fromY<<4);
11751     int piece = quickBoard[sq];
11752     quickBoard[sq] = 0;
11753     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11754     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11755         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11756         moveDatabase[movePtr++].piece = Q_WCASTL;
11757         quickBoard[sq] = piece;
11758         piece = quickBoard[from]; quickBoard[from] = 0;
11759         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11760     } else
11761     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11762         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11763         moveDatabase[movePtr++].piece = Q_BCASTL;
11764         quickBoard[sq] = piece;
11765         piece = quickBoard[from]; quickBoard[from] = 0;
11766         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11767     } else
11768     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11769         quickBoard[(fromY<<4)+toX] = 0;
11770         moveDatabase[movePtr].piece = Q_EP;
11771         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11772         moveDatabase[movePtr].to = sq;
11773     } else
11774     if(promoPiece != pieceType[piece]) {
11775         moveDatabase[movePtr++].piece = Q_PROMO;
11776         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11777     }
11778     moveDatabase[movePtr].piece = piece;
11779     quickBoard[sq] = piece;
11780     movePtr++;
11781 }
11782
11783 int
11784 PackGame (Board board)
11785 {
11786     Move *newSpace = NULL;
11787     moveDatabase[movePtr].piece = 0; // terminate previous game
11788     if(movePtr > dataSize) {
11789         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11790         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11791         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11792         if(newSpace) {
11793             int i;
11794             Move *p = moveDatabase, *q = newSpace;
11795             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11796             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11797             moveDatabase = newSpace;
11798         } else { // calloc failed, we must be out of memory. Too bad...
11799             dataSize = 0; // prevent calloc events for all subsequent games
11800             return 0;     // and signal this one isn't cached
11801         }
11802     }
11803     movePtr++;
11804     MakePieceList(board, counts);
11805     return movePtr;
11806 }
11807
11808 int
11809 QuickCompare (Board board, int *minCounts, int *maxCounts)
11810 {   // compare according to search mode
11811     int r, f;
11812     switch(appData.searchMode)
11813     {
11814       case 1: // exact position match
11815         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11816         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11817             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11818         }
11819         break;
11820       case 2: // can have extra material on empty squares
11821         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11822             if(board[r][f] == EmptySquare) continue;
11823             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11824         }
11825         break;
11826       case 3: // material with exact Pawn structure
11827         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11828             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11829             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11830         } // fall through to material comparison
11831       case 4: // exact material
11832         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11833         break;
11834       case 6: // material range with given imbalance
11835         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11836         // fall through to range comparison
11837       case 5: // material range
11838         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11839     }
11840     return TRUE;
11841 }
11842
11843 int
11844 QuickScan (Board board, Move *move)
11845 {   // reconstruct game,and compare all positions in it
11846     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11847     do {
11848         int piece = move->piece;
11849         int to = move->to, from = pieceList[piece];
11850         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11851           if(!piece) return -1;
11852           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11853             piece = (++move)->piece;
11854             from = pieceList[piece];
11855             counts[pieceType[piece]]--;
11856             pieceType[piece] = (ChessSquare) move->to;
11857             counts[move->to]++;
11858           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11859             counts[pieceType[quickBoard[to]]]--;
11860             quickBoard[to] = 0; total--;
11861             move++;
11862             continue;
11863           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11864             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11865             from  = pieceList[piece]; // so this must be King
11866             quickBoard[from] = 0;
11867             pieceList[piece] = to;
11868             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11869             quickBoard[from] = 0; // rook
11870             quickBoard[to] = piece;
11871             to = move->to; piece = move->piece;
11872             goto aftercastle;
11873           }
11874         }
11875         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11876         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11877         quickBoard[from] = 0;
11878       aftercastle:
11879         quickBoard[to] = piece;
11880         pieceList[piece] = to;
11881         cnt++; turn ^= 3;
11882         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11883            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11884            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11885                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11886           ) {
11887             static int lastCounts[EmptySquare+1];
11888             int i;
11889             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11890             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11891         } else stretch = 0;
11892         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11893         move++;
11894     } while(1);
11895 }
11896
11897 void
11898 InitSearch ()
11899 {
11900     int r, f;
11901     flipSearch = FALSE;
11902     CopyBoard(soughtBoard, boards[currentMove]);
11903     soughtTotal = MakePieceList(soughtBoard, maxSought);
11904     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11905     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11906     CopyBoard(reverseBoard, boards[currentMove]);
11907     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11908         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11909         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11910         reverseBoard[r][f] = piece;
11911     }
11912     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11913     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11914     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11915                  || (boards[currentMove][CASTLING][2] == NoRights ||
11916                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11917                  && (boards[currentMove][CASTLING][5] == NoRights ||
11918                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11919       ) {
11920         flipSearch = TRUE;
11921         CopyBoard(flipBoard, soughtBoard);
11922         CopyBoard(rotateBoard, reverseBoard);
11923         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11924             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11925             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11926         }
11927     }
11928     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11929     if(appData.searchMode >= 5) {
11930         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11931         MakePieceList(soughtBoard, minSought);
11932         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11933     }
11934     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11935         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11936 }
11937
11938 GameInfo dummyInfo;
11939 static int creatingBook;
11940
11941 int
11942 GameContainsPosition (FILE *f, ListGame *lg)
11943 {
11944     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11945     int fromX, fromY, toX, toY;
11946     char promoChar;
11947     static int initDone=FALSE;
11948
11949     // weed out games based on numerical tag comparison
11950     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11951     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11952     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11953     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11954     if(!initDone) {
11955         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11956         initDone = TRUE;
11957     }
11958     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11959     else CopyBoard(boards[scratch], initialPosition); // default start position
11960     if(lg->moves) {
11961         turn = btm + 1;
11962         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11963         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11964     }
11965     if(btm) plyNr++;
11966     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11967     fseek(f, lg->offset, 0);
11968     yynewfile(f);
11969     while(1) {
11970         yyboardindex = scratch;
11971         quickFlag = plyNr+1;
11972         next = Myylex();
11973         quickFlag = 0;
11974         switch(next) {
11975             case PGNTag:
11976                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11977             default:
11978                 continue;
11979
11980             case XBoardGame:
11981             case GNUChessGame:
11982                 if(plyNr) return -1; // after we have seen moves, this is for new game
11983               continue;
11984
11985             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11986             case ImpossibleMove:
11987             case WhiteWins: // game ends here with these four
11988             case BlackWins:
11989             case GameIsDrawn:
11990             case GameUnfinished:
11991                 return -1;
11992
11993             case IllegalMove:
11994                 if(appData.testLegality) return -1;
11995             case WhiteCapturesEnPassant:
11996             case BlackCapturesEnPassant:
11997             case WhitePromotion:
11998             case BlackPromotion:
11999             case WhiteNonPromotion:
12000             case BlackNonPromotion:
12001             case NormalMove:
12002             case WhiteKingSideCastle:
12003             case WhiteQueenSideCastle:
12004             case BlackKingSideCastle:
12005             case BlackQueenSideCastle:
12006             case WhiteKingSideCastleWild:
12007             case WhiteQueenSideCastleWild:
12008             case BlackKingSideCastleWild:
12009             case BlackQueenSideCastleWild:
12010             case WhiteHSideCastleFR:
12011             case WhiteASideCastleFR:
12012             case BlackHSideCastleFR:
12013             case BlackASideCastleFR:
12014                 fromX = currentMoveString[0] - AAA;
12015                 fromY = currentMoveString[1] - ONE;
12016                 toX = currentMoveString[2] - AAA;
12017                 toY = currentMoveString[3] - ONE;
12018                 promoChar = currentMoveString[4];
12019                 break;
12020             case WhiteDrop:
12021             case BlackDrop:
12022                 fromX = next == WhiteDrop ?
12023                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12024                   (int) CharToPiece(ToLower(currentMoveString[0]));
12025                 fromY = DROP_RANK;
12026                 toX = currentMoveString[2] - AAA;
12027                 toY = currentMoveString[3] - ONE;
12028                 promoChar = 0;
12029                 break;
12030         }
12031         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12032         plyNr++;
12033         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12034         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12035         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12036         if(appData.findMirror) {
12037             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12038             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12039         }
12040     }
12041 }
12042
12043 /* Load the nth game from open file f */
12044 int
12045 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12046 {
12047     ChessMove cm;
12048     char buf[MSG_SIZ];
12049     int gn = gameNumber;
12050     ListGame *lg = NULL;
12051     int numPGNTags = 0;
12052     int err, pos = -1;
12053     GameMode oldGameMode;
12054     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12055
12056     if (appData.debugMode)
12057         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12058
12059     if (gameMode == Training )
12060         SetTrainingModeOff();
12061
12062     oldGameMode = gameMode;
12063     if (gameMode != BeginningOfGame) {
12064       Reset(FALSE, TRUE);
12065     }
12066
12067     gameFileFP = f;
12068     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12069         fclose(lastLoadGameFP);
12070     }
12071
12072     if (useList) {
12073         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12074
12075         if (lg) {
12076             fseek(f, lg->offset, 0);
12077             GameListHighlight(gameNumber);
12078             pos = lg->position;
12079             gn = 1;
12080         }
12081         else {
12082             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12083               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12084             else
12085             DisplayError(_("Game number out of range"), 0);
12086             return FALSE;
12087         }
12088     } else {
12089         GameListDestroy();
12090         if (fseek(f, 0, 0) == -1) {
12091             if (f == lastLoadGameFP ?
12092                 gameNumber == lastLoadGameNumber + 1 :
12093                 gameNumber == 1) {
12094                 gn = 1;
12095             } else {
12096                 DisplayError(_("Can't seek on game file"), 0);
12097                 return FALSE;
12098             }
12099         }
12100     }
12101     lastLoadGameFP = f;
12102     lastLoadGameNumber = gameNumber;
12103     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12104     lastLoadGameUseList = useList;
12105
12106     yynewfile(f);
12107
12108     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12109       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12110                 lg->gameInfo.black);
12111             DisplayTitle(buf);
12112     } else if (*title != NULLCHAR) {
12113         if (gameNumber > 1) {
12114           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12115             DisplayTitle(buf);
12116         } else {
12117             DisplayTitle(title);
12118         }
12119     }
12120
12121     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12122         gameMode = PlayFromGameFile;
12123         ModeHighlight();
12124     }
12125
12126     currentMove = forwardMostMove = backwardMostMove = 0;
12127     CopyBoard(boards[0], initialPosition);
12128     StopClocks();
12129
12130     /*
12131      * Skip the first gn-1 games in the file.
12132      * Also skip over anything that precedes an identifiable
12133      * start of game marker, to avoid being confused by
12134      * garbage at the start of the file.  Currently
12135      * recognized start of game markers are the move number "1",
12136      * the pattern "gnuchess .* game", the pattern
12137      * "^[#;%] [^ ]* game file", and a PGN tag block.
12138      * A game that starts with one of the latter two patterns
12139      * will also have a move number 1, possibly
12140      * following a position diagram.
12141      * 5-4-02: Let's try being more lenient and allowing a game to
12142      * start with an unnumbered move.  Does that break anything?
12143      */
12144     cm = lastLoadGameStart = EndOfFile;
12145     while (gn > 0) {
12146         yyboardindex = forwardMostMove;
12147         cm = (ChessMove) Myylex();
12148         switch (cm) {
12149           case EndOfFile:
12150             if (cmailMsgLoaded) {
12151                 nCmailGames = CMAIL_MAX_GAMES - gn;
12152             } else {
12153                 Reset(TRUE, TRUE);
12154                 DisplayError(_("Game not found in file"), 0);
12155             }
12156             return FALSE;
12157
12158           case GNUChessGame:
12159           case XBoardGame:
12160             gn--;
12161             lastLoadGameStart = cm;
12162             break;
12163
12164           case MoveNumberOne:
12165             switch (lastLoadGameStart) {
12166               case GNUChessGame:
12167               case XBoardGame:
12168               case PGNTag:
12169                 break;
12170               case MoveNumberOne:
12171               case EndOfFile:
12172                 gn--;           /* count this game */
12173                 lastLoadGameStart = cm;
12174                 break;
12175               default:
12176                 /* impossible */
12177                 break;
12178             }
12179             break;
12180
12181           case PGNTag:
12182             switch (lastLoadGameStart) {
12183               case GNUChessGame:
12184               case PGNTag:
12185               case MoveNumberOne:
12186               case EndOfFile:
12187                 gn--;           /* count this game */
12188                 lastLoadGameStart = cm;
12189                 break;
12190               case XBoardGame:
12191                 lastLoadGameStart = cm; /* game counted already */
12192                 break;
12193               default:
12194                 /* impossible */
12195                 break;
12196             }
12197             if (gn > 0) {
12198                 do {
12199                     yyboardindex = forwardMostMove;
12200                     cm = (ChessMove) Myylex();
12201                 } while (cm == PGNTag || cm == Comment);
12202             }
12203             break;
12204
12205           case WhiteWins:
12206           case BlackWins:
12207           case GameIsDrawn:
12208             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12209                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12210                     != CMAIL_OLD_RESULT) {
12211                     nCmailResults ++ ;
12212                     cmailResult[  CMAIL_MAX_GAMES
12213                                 - gn - 1] = CMAIL_OLD_RESULT;
12214                 }
12215             }
12216             break;
12217
12218           case NormalMove:
12219             /* Only a NormalMove can be at the start of a game
12220              * without a position diagram. */
12221             if (lastLoadGameStart == EndOfFile ) {
12222               gn--;
12223               lastLoadGameStart = MoveNumberOne;
12224             }
12225             break;
12226
12227           default:
12228             break;
12229         }
12230     }
12231
12232     if (appData.debugMode)
12233       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12234
12235     if (cm == XBoardGame) {
12236         /* Skip any header junk before position diagram and/or move 1 */
12237         for (;;) {
12238             yyboardindex = forwardMostMove;
12239             cm = (ChessMove) Myylex();
12240
12241             if (cm == EndOfFile ||
12242                 cm == GNUChessGame || cm == XBoardGame) {
12243                 /* Empty game; pretend end-of-file and handle later */
12244                 cm = EndOfFile;
12245                 break;
12246             }
12247
12248             if (cm == MoveNumberOne || cm == PositionDiagram ||
12249                 cm == PGNTag || cm == Comment)
12250               break;
12251         }
12252     } else if (cm == GNUChessGame) {
12253         if (gameInfo.event != NULL) {
12254             free(gameInfo.event);
12255         }
12256         gameInfo.event = StrSave(yy_text);
12257     }
12258
12259     startedFromSetupPosition = FALSE;
12260     while (cm == PGNTag) {
12261         if (appData.debugMode)
12262           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12263         err = ParsePGNTag(yy_text, &gameInfo);
12264         if (!err) numPGNTags++;
12265
12266         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12267         if(gameInfo.variant != oldVariant) {
12268             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12269             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12270             InitPosition(TRUE);
12271             oldVariant = gameInfo.variant;
12272             if (appData.debugMode)
12273               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12274         }
12275
12276
12277         if (gameInfo.fen != NULL) {
12278           Board initial_position;
12279           startedFromSetupPosition = TRUE;
12280           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12281             Reset(TRUE, TRUE);
12282             DisplayError(_("Bad FEN position in file"), 0);
12283             return FALSE;
12284           }
12285           CopyBoard(boards[0], initial_position);
12286           if (blackPlaysFirst) {
12287             currentMove = forwardMostMove = backwardMostMove = 1;
12288             CopyBoard(boards[1], initial_position);
12289             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12290             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12291             timeRemaining[0][1] = whiteTimeRemaining;
12292             timeRemaining[1][1] = blackTimeRemaining;
12293             if (commentList[0] != NULL) {
12294               commentList[1] = commentList[0];
12295               commentList[0] = NULL;
12296             }
12297           } else {
12298             currentMove = forwardMostMove = backwardMostMove = 0;
12299           }
12300           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12301           {   int i;
12302               initialRulePlies = FENrulePlies;
12303               for( i=0; i< nrCastlingRights; i++ )
12304                   initialRights[i] = initial_position[CASTLING][i];
12305           }
12306           yyboardindex = forwardMostMove;
12307           free(gameInfo.fen);
12308           gameInfo.fen = NULL;
12309         }
12310
12311         yyboardindex = forwardMostMove;
12312         cm = (ChessMove) Myylex();
12313
12314         /* Handle comments interspersed among the tags */
12315         while (cm == Comment) {
12316             char *p;
12317             if (appData.debugMode)
12318               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12319             p = yy_text;
12320             AppendComment(currentMove, p, FALSE);
12321             yyboardindex = forwardMostMove;
12322             cm = (ChessMove) Myylex();
12323         }
12324     }
12325
12326     /* don't rely on existence of Event tag since if game was
12327      * pasted from clipboard the Event tag may not exist
12328      */
12329     if (numPGNTags > 0){
12330         char *tags;
12331         if (gameInfo.variant == VariantNormal) {
12332           VariantClass v = StringToVariant(gameInfo.event);
12333           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12334           if(v < VariantShogi) gameInfo.variant = v;
12335         }
12336         if (!matchMode) {
12337           if( appData.autoDisplayTags ) {
12338             tags = PGNTags(&gameInfo);
12339             TagsPopUp(tags, CmailMsg());
12340             free(tags);
12341           }
12342         }
12343     } else {
12344         /* Make something up, but don't display it now */
12345         SetGameInfo();
12346         TagsPopDown();
12347     }
12348
12349     if (cm == PositionDiagram) {
12350         int i, j;
12351         char *p;
12352         Board initial_position;
12353
12354         if (appData.debugMode)
12355           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12356
12357         if (!startedFromSetupPosition) {
12358             p = yy_text;
12359             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12360               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12361                 switch (*p) {
12362                   case '{':
12363                   case '[':
12364                   case '-':
12365                   case ' ':
12366                   case '\t':
12367                   case '\n':
12368                   case '\r':
12369                     break;
12370                   default:
12371                     initial_position[i][j++] = CharToPiece(*p);
12372                     break;
12373                 }
12374             while (*p == ' ' || *p == '\t' ||
12375                    *p == '\n' || *p == '\r') p++;
12376
12377             if (strncmp(p, "black", strlen("black"))==0)
12378               blackPlaysFirst = TRUE;
12379             else
12380               blackPlaysFirst = FALSE;
12381             startedFromSetupPosition = TRUE;
12382
12383             CopyBoard(boards[0], initial_position);
12384             if (blackPlaysFirst) {
12385                 currentMove = forwardMostMove = backwardMostMove = 1;
12386                 CopyBoard(boards[1], initial_position);
12387                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12388                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12389                 timeRemaining[0][1] = whiteTimeRemaining;
12390                 timeRemaining[1][1] = blackTimeRemaining;
12391                 if (commentList[0] != NULL) {
12392                     commentList[1] = commentList[0];
12393                     commentList[0] = NULL;
12394                 }
12395             } else {
12396                 currentMove = forwardMostMove = backwardMostMove = 0;
12397             }
12398         }
12399         yyboardindex = forwardMostMove;
12400         cm = (ChessMove) Myylex();
12401     }
12402
12403   if(!creatingBook) {
12404     if (first.pr == NoProc) {
12405         StartChessProgram(&first);
12406     }
12407     InitChessProgram(&first, FALSE);
12408     SendToProgram("force\n", &first);
12409     if (startedFromSetupPosition) {
12410         SendBoard(&first, forwardMostMove);
12411     if (appData.debugMode) {
12412         fprintf(debugFP, "Load Game\n");
12413     }
12414         DisplayBothClocks();
12415     }
12416   }
12417
12418     /* [HGM] server: flag to write setup moves in broadcast file as one */
12419     loadFlag = appData.suppressLoadMoves;
12420
12421     while (cm == Comment) {
12422         char *p;
12423         if (appData.debugMode)
12424           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12425         p = yy_text;
12426         AppendComment(currentMove, p, FALSE);
12427         yyboardindex = forwardMostMove;
12428         cm = (ChessMove) Myylex();
12429     }
12430
12431     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12432         cm == WhiteWins || cm == BlackWins ||
12433         cm == GameIsDrawn || cm == GameUnfinished) {
12434         DisplayMessage("", _("No moves in game"));
12435         if (cmailMsgLoaded) {
12436             if (appData.debugMode)
12437               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12438             ClearHighlights();
12439             flipView = FALSE;
12440         }
12441         DrawPosition(FALSE, boards[currentMove]);
12442         DisplayBothClocks();
12443         gameMode = EditGame;
12444         ModeHighlight();
12445         gameFileFP = NULL;
12446         cmailOldMove = 0;
12447         return TRUE;
12448     }
12449
12450     // [HGM] PV info: routine tests if comment empty
12451     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12452         DisplayComment(currentMove - 1, commentList[currentMove]);
12453     }
12454     if (!matchMode && appData.timeDelay != 0)
12455       DrawPosition(FALSE, boards[currentMove]);
12456
12457     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12458       programStats.ok_to_send = 1;
12459     }
12460
12461     /* if the first token after the PGN tags is a move
12462      * and not move number 1, retrieve it from the parser
12463      */
12464     if (cm != MoveNumberOne)
12465         LoadGameOneMove(cm);
12466
12467     /* load the remaining moves from the file */
12468     while (LoadGameOneMove(EndOfFile)) {
12469       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12470       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12471     }
12472
12473     /* rewind to the start of the game */
12474     currentMove = backwardMostMove;
12475
12476     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12477
12478     if (oldGameMode == AnalyzeFile) {
12479       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12480       AnalyzeFileEvent();
12481     } else
12482     if (oldGameMode == AnalyzeMode) {
12483       AnalyzeFileEvent();
12484     }
12485
12486     if(creatingBook) return TRUE;
12487     if (!matchMode && pos > 0) {
12488         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12489     } else
12490     if (matchMode || appData.timeDelay == 0) {
12491       ToEndEvent();
12492     } else if (appData.timeDelay > 0) {
12493       AutoPlayGameLoop();
12494     }
12495
12496     if (appData.debugMode)
12497         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12498
12499     loadFlag = 0; /* [HGM] true game starts */
12500     return TRUE;
12501 }
12502
12503 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12504 int
12505 ReloadPosition (int offset)
12506 {
12507     int positionNumber = lastLoadPositionNumber + offset;
12508     if (lastLoadPositionFP == NULL) {
12509         DisplayError(_("No position has been loaded yet"), 0);
12510         return FALSE;
12511     }
12512     if (positionNumber <= 0) {
12513         DisplayError(_("Can't back up any further"), 0);
12514         return FALSE;
12515     }
12516     return LoadPosition(lastLoadPositionFP, positionNumber,
12517                         lastLoadPositionTitle);
12518 }
12519
12520 /* Load the nth position from the given file */
12521 int
12522 LoadPositionFromFile (char *filename, int n, char *title)
12523 {
12524     FILE *f;
12525     char buf[MSG_SIZ];
12526
12527     if (strcmp(filename, "-") == 0) {
12528         return LoadPosition(stdin, n, "stdin");
12529     } else {
12530         f = fopen(filename, "rb");
12531         if (f == NULL) {
12532             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12533             DisplayError(buf, errno);
12534             return FALSE;
12535         } else {
12536             return LoadPosition(f, n, title);
12537         }
12538     }
12539 }
12540
12541 /* Load the nth position from the given open file, and close it */
12542 int
12543 LoadPosition (FILE *f, int positionNumber, char *title)
12544 {
12545     char *p, line[MSG_SIZ];
12546     Board initial_position;
12547     int i, j, fenMode, pn;
12548
12549     if (gameMode == Training )
12550         SetTrainingModeOff();
12551
12552     if (gameMode != BeginningOfGame) {
12553         Reset(FALSE, TRUE);
12554     }
12555     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12556         fclose(lastLoadPositionFP);
12557     }
12558     if (positionNumber == 0) positionNumber = 1;
12559     lastLoadPositionFP = f;
12560     lastLoadPositionNumber = positionNumber;
12561     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12562     if (first.pr == NoProc && !appData.noChessProgram) {
12563       StartChessProgram(&first);
12564       InitChessProgram(&first, FALSE);
12565     }
12566     pn = positionNumber;
12567     if (positionNumber < 0) {
12568         /* Negative position number means to seek to that byte offset */
12569         if (fseek(f, -positionNumber, 0) == -1) {
12570             DisplayError(_("Can't seek on position file"), 0);
12571             return FALSE;
12572         };
12573         pn = 1;
12574     } else {
12575         if (fseek(f, 0, 0) == -1) {
12576             if (f == lastLoadPositionFP ?
12577                 positionNumber == lastLoadPositionNumber + 1 :
12578                 positionNumber == 1) {
12579                 pn = 1;
12580             } else {
12581                 DisplayError(_("Can't seek on position file"), 0);
12582                 return FALSE;
12583             }
12584         }
12585     }
12586     /* See if this file is FEN or old-style xboard */
12587     if (fgets(line, MSG_SIZ, f) == NULL) {
12588         DisplayError(_("Position not found in file"), 0);
12589         return FALSE;
12590     }
12591     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12592     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12593
12594     if (pn >= 2) {
12595         if (fenMode || line[0] == '#') pn--;
12596         while (pn > 0) {
12597             /* skip positions before number pn */
12598             if (fgets(line, MSG_SIZ, f) == NULL) {
12599                 Reset(TRUE, TRUE);
12600                 DisplayError(_("Position not found in file"), 0);
12601                 return FALSE;
12602             }
12603             if (fenMode || line[0] == '#') pn--;
12604         }
12605     }
12606
12607     if (fenMode) {
12608         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12609             DisplayError(_("Bad FEN position in file"), 0);
12610             return FALSE;
12611         }
12612     } else {
12613         (void) fgets(line, MSG_SIZ, f);
12614         (void) fgets(line, MSG_SIZ, f);
12615
12616         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12617             (void) fgets(line, MSG_SIZ, f);
12618             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12619                 if (*p == ' ')
12620                   continue;
12621                 initial_position[i][j++] = CharToPiece(*p);
12622             }
12623         }
12624
12625         blackPlaysFirst = FALSE;
12626         if (!feof(f)) {
12627             (void) fgets(line, MSG_SIZ, f);
12628             if (strncmp(line, "black", strlen("black"))==0)
12629               blackPlaysFirst = TRUE;
12630         }
12631     }
12632     startedFromSetupPosition = TRUE;
12633
12634     CopyBoard(boards[0], initial_position);
12635     if (blackPlaysFirst) {
12636         currentMove = forwardMostMove = backwardMostMove = 1;
12637         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12638         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12639         CopyBoard(boards[1], initial_position);
12640         DisplayMessage("", _("Black to play"));
12641     } else {
12642         currentMove = forwardMostMove = backwardMostMove = 0;
12643         DisplayMessage("", _("White to play"));
12644     }
12645     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12646     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12647         SendToProgram("force\n", &first);
12648         SendBoard(&first, forwardMostMove);
12649     }
12650     if (appData.debugMode) {
12651 int i, j;
12652   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12653   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12654         fprintf(debugFP, "Load Position\n");
12655     }
12656
12657     if (positionNumber > 1) {
12658       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12659         DisplayTitle(line);
12660     } else {
12661         DisplayTitle(title);
12662     }
12663     gameMode = EditGame;
12664     ModeHighlight();
12665     ResetClocks();
12666     timeRemaining[0][1] = whiteTimeRemaining;
12667     timeRemaining[1][1] = blackTimeRemaining;
12668     DrawPosition(FALSE, boards[currentMove]);
12669
12670     return TRUE;
12671 }
12672
12673
12674 void
12675 CopyPlayerNameIntoFileName (char **dest, char *src)
12676 {
12677     while (*src != NULLCHAR && *src != ',') {
12678         if (*src == ' ') {
12679             *(*dest)++ = '_';
12680             src++;
12681         } else {
12682             *(*dest)++ = *src++;
12683         }
12684     }
12685 }
12686
12687 char *
12688 DefaultFileName (char *ext)
12689 {
12690     static char def[MSG_SIZ];
12691     char *p;
12692
12693     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12694         p = def;
12695         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12696         *p++ = '-';
12697         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12698         *p++ = '.';
12699         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12700     } else {
12701         def[0] = NULLCHAR;
12702     }
12703     return def;
12704 }
12705
12706 /* Save the current game to the given file */
12707 int
12708 SaveGameToFile (char *filename, int append)
12709 {
12710     FILE *f;
12711     char buf[MSG_SIZ];
12712     int result, i, t,tot=0;
12713
12714     if (strcmp(filename, "-") == 0) {
12715         return SaveGame(stdout, 0, NULL);
12716     } else {
12717         for(i=0; i<10; i++) { // upto 10 tries
12718              f = fopen(filename, append ? "a" : "w");
12719              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12720              if(f || errno != 13) break;
12721              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12722              tot += t;
12723         }
12724         if (f == NULL) {
12725             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12726             DisplayError(buf, errno);
12727             return FALSE;
12728         } else {
12729             safeStrCpy(buf, lastMsg, MSG_SIZ);
12730             DisplayMessage(_("Waiting for access to save file"), "");
12731             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12732             DisplayMessage(_("Saving game"), "");
12733             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12734             result = SaveGame(f, 0, NULL);
12735             DisplayMessage(buf, "");
12736             return result;
12737         }
12738     }
12739 }
12740
12741 char *
12742 SavePart (char *str)
12743 {
12744     static char buf[MSG_SIZ];
12745     char *p;
12746
12747     p = strchr(str, ' ');
12748     if (p == NULL) return str;
12749     strncpy(buf, str, p - str);
12750     buf[p - str] = NULLCHAR;
12751     return buf;
12752 }
12753
12754 #define PGN_MAX_LINE 75
12755
12756 #define PGN_SIDE_WHITE  0
12757 #define PGN_SIDE_BLACK  1
12758
12759 static int
12760 FindFirstMoveOutOfBook (int side)
12761 {
12762     int result = -1;
12763
12764     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12765         int index = backwardMostMove;
12766         int has_book_hit = 0;
12767
12768         if( (index % 2) != side ) {
12769             index++;
12770         }
12771
12772         while( index < forwardMostMove ) {
12773             /* Check to see if engine is in book */
12774             int depth = pvInfoList[index].depth;
12775             int score = pvInfoList[index].score;
12776             int in_book = 0;
12777
12778             if( depth <= 2 ) {
12779                 in_book = 1;
12780             }
12781             else if( score == 0 && depth == 63 ) {
12782                 in_book = 1; /* Zappa */
12783             }
12784             else if( score == 2 && depth == 99 ) {
12785                 in_book = 1; /* Abrok */
12786             }
12787
12788             has_book_hit += in_book;
12789
12790             if( ! in_book ) {
12791                 result = index;
12792
12793                 break;
12794             }
12795
12796             index += 2;
12797         }
12798     }
12799
12800     return result;
12801 }
12802
12803 void
12804 GetOutOfBookInfo (char * buf)
12805 {
12806     int oob[2];
12807     int i;
12808     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12809
12810     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12811     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12812
12813     *buf = '\0';
12814
12815     if( oob[0] >= 0 || oob[1] >= 0 ) {
12816         for( i=0; i<2; i++ ) {
12817             int idx = oob[i];
12818
12819             if( idx >= 0 ) {
12820                 if( i > 0 && oob[0] >= 0 ) {
12821                     strcat( buf, "   " );
12822                 }
12823
12824                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12825                 sprintf( buf+strlen(buf), "%s%.2f",
12826                     pvInfoList[idx].score >= 0 ? "+" : "",
12827                     pvInfoList[idx].score / 100.0 );
12828             }
12829         }
12830     }
12831 }
12832
12833 /* Save game in PGN style and close the file */
12834 int
12835 SaveGamePGN (FILE *f)
12836 {
12837     int i, offset, linelen, newblock;
12838 //    char *movetext;
12839     char numtext[32];
12840     int movelen, numlen, blank;
12841     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12842
12843     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12844
12845     PrintPGNTags(f, &gameInfo);
12846
12847     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12848
12849     if (backwardMostMove > 0 || startedFromSetupPosition) {
12850         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
12851         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12852         fprintf(f, "\n{--------------\n");
12853         PrintPosition(f, backwardMostMove);
12854         fprintf(f, "--------------}\n");
12855         free(fen);
12856     }
12857     else {
12858         /* [AS] Out of book annotation */
12859         if( appData.saveOutOfBookInfo ) {
12860             char buf[64];
12861
12862             GetOutOfBookInfo( buf );
12863
12864             if( buf[0] != '\0' ) {
12865                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12866             }
12867         }
12868
12869         fprintf(f, "\n");
12870     }
12871
12872     i = backwardMostMove;
12873     linelen = 0;
12874     newblock = TRUE;
12875
12876     while (i < forwardMostMove) {
12877         /* Print comments preceding this move */
12878         if (commentList[i] != NULL) {
12879             if (linelen > 0) fprintf(f, "\n");
12880             fprintf(f, "%s", commentList[i]);
12881             linelen = 0;
12882             newblock = TRUE;
12883         }
12884
12885         /* Format move number */
12886         if ((i % 2) == 0)
12887           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12888         else
12889           if (newblock)
12890             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12891           else
12892             numtext[0] = NULLCHAR;
12893
12894         numlen = strlen(numtext);
12895         newblock = FALSE;
12896
12897         /* Print move number */
12898         blank = linelen > 0 && numlen > 0;
12899         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12900             fprintf(f, "\n");
12901             linelen = 0;
12902             blank = 0;
12903         }
12904         if (blank) {
12905             fprintf(f, " ");
12906             linelen++;
12907         }
12908         fprintf(f, "%s", numtext);
12909         linelen += numlen;
12910
12911         /* Get move */
12912         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12913         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12914
12915         /* Print move */
12916         blank = linelen > 0 && movelen > 0;
12917         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12918             fprintf(f, "\n");
12919             linelen = 0;
12920             blank = 0;
12921         }
12922         if (blank) {
12923             fprintf(f, " ");
12924             linelen++;
12925         }
12926         fprintf(f, "%s", move_buffer);
12927         linelen += movelen;
12928
12929         /* [AS] Add PV info if present */
12930         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12931             /* [HGM] add time */
12932             char buf[MSG_SIZ]; int seconds;
12933
12934             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12935
12936             if( seconds <= 0)
12937               buf[0] = 0;
12938             else
12939               if( seconds < 30 )
12940                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12941               else
12942                 {
12943                   seconds = (seconds + 4)/10; // round to full seconds
12944                   if( seconds < 60 )
12945                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12946                   else
12947                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12948                 }
12949
12950             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12951                       pvInfoList[i].score >= 0 ? "+" : "",
12952                       pvInfoList[i].score / 100.0,
12953                       pvInfoList[i].depth,
12954                       buf );
12955
12956             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12957
12958             /* Print score/depth */
12959             blank = linelen > 0 && movelen > 0;
12960             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12961                 fprintf(f, "\n");
12962                 linelen = 0;
12963                 blank = 0;
12964             }
12965             if (blank) {
12966                 fprintf(f, " ");
12967                 linelen++;
12968             }
12969             fprintf(f, "%s", move_buffer);
12970             linelen += movelen;
12971         }
12972
12973         i++;
12974     }
12975
12976     /* Start a new line */
12977     if (linelen > 0) fprintf(f, "\n");
12978
12979     /* Print comments after last move */
12980     if (commentList[i] != NULL) {
12981         fprintf(f, "%s\n", commentList[i]);
12982     }
12983
12984     /* Print result */
12985     if (gameInfo.resultDetails != NULL &&
12986         gameInfo.resultDetails[0] != NULLCHAR) {
12987         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12988                 PGNResult(gameInfo.result));
12989     } else {
12990         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12991     }
12992
12993     fclose(f);
12994     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12995     return TRUE;
12996 }
12997
12998 /* Save game in old style and close the file */
12999 int
13000 SaveGameOldStyle (FILE *f)
13001 {
13002     int i, offset;
13003     time_t tm;
13004
13005     tm = time((time_t *) NULL);
13006
13007     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13008     PrintOpponents(f);
13009
13010     if (backwardMostMove > 0 || startedFromSetupPosition) {
13011         fprintf(f, "\n[--------------\n");
13012         PrintPosition(f, backwardMostMove);
13013         fprintf(f, "--------------]\n");
13014     } else {
13015         fprintf(f, "\n");
13016     }
13017
13018     i = backwardMostMove;
13019     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13020
13021     while (i < forwardMostMove) {
13022         if (commentList[i] != NULL) {
13023             fprintf(f, "[%s]\n", commentList[i]);
13024         }
13025
13026         if ((i % 2) == 1) {
13027             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13028             i++;
13029         } else {
13030             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13031             i++;
13032             if (commentList[i] != NULL) {
13033                 fprintf(f, "\n");
13034                 continue;
13035             }
13036             if (i >= forwardMostMove) {
13037                 fprintf(f, "\n");
13038                 break;
13039             }
13040             fprintf(f, "%s\n", parseList[i]);
13041             i++;
13042         }
13043     }
13044
13045     if (commentList[i] != NULL) {
13046         fprintf(f, "[%s]\n", commentList[i]);
13047     }
13048
13049     /* This isn't really the old style, but it's close enough */
13050     if (gameInfo.resultDetails != NULL &&
13051         gameInfo.resultDetails[0] != NULLCHAR) {
13052         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13053                 gameInfo.resultDetails);
13054     } else {
13055         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13056     }
13057
13058     fclose(f);
13059     return TRUE;
13060 }
13061
13062 /* Save the current game to open file f and close the file */
13063 int
13064 SaveGame (FILE *f, int dummy, char *dummy2)
13065 {
13066     if (gameMode == EditPosition) EditPositionDone(TRUE);
13067     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13068     if (appData.oldSaveStyle)
13069       return SaveGameOldStyle(f);
13070     else
13071       return SaveGamePGN(f);
13072 }
13073
13074 /* Save the current position to the given file */
13075 int
13076 SavePositionToFile (char *filename)
13077 {
13078     FILE *f;
13079     char buf[MSG_SIZ];
13080
13081     if (strcmp(filename, "-") == 0) {
13082         return SavePosition(stdout, 0, NULL);
13083     } else {
13084         f = fopen(filename, "a");
13085         if (f == NULL) {
13086             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13087             DisplayError(buf, errno);
13088             return FALSE;
13089         } else {
13090             safeStrCpy(buf, lastMsg, MSG_SIZ);
13091             DisplayMessage(_("Waiting for access to save file"), "");
13092             flock(fileno(f), LOCK_EX); // [HGM] lock
13093             DisplayMessage(_("Saving position"), "");
13094             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13095             SavePosition(f, 0, NULL);
13096             DisplayMessage(buf, "");
13097             return TRUE;
13098         }
13099     }
13100 }
13101
13102 /* Save the current position to the given open file and close the file */
13103 int
13104 SavePosition (FILE *f, int dummy, char *dummy2)
13105 {
13106     time_t tm;
13107     char *fen;
13108
13109     if (gameMode == EditPosition) EditPositionDone(TRUE);
13110     if (appData.oldSaveStyle) {
13111         tm = time((time_t *) NULL);
13112
13113         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13114         PrintOpponents(f);
13115         fprintf(f, "[--------------\n");
13116         PrintPosition(f, currentMove);
13117         fprintf(f, "--------------]\n");
13118     } else {
13119         fen = PositionToFEN(currentMove, NULL, 1);
13120         fprintf(f, "%s\n", fen);
13121         free(fen);
13122     }
13123     fclose(f);
13124     return TRUE;
13125 }
13126
13127 void
13128 ReloadCmailMsgEvent (int unregister)
13129 {
13130 #if !WIN32
13131     static char *inFilename = NULL;
13132     static char *outFilename;
13133     int i;
13134     struct stat inbuf, outbuf;
13135     int status;
13136
13137     /* Any registered moves are unregistered if unregister is set, */
13138     /* i.e. invoked by the signal handler */
13139     if (unregister) {
13140         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13141             cmailMoveRegistered[i] = FALSE;
13142             if (cmailCommentList[i] != NULL) {
13143                 free(cmailCommentList[i]);
13144                 cmailCommentList[i] = NULL;
13145             }
13146         }
13147         nCmailMovesRegistered = 0;
13148     }
13149
13150     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13151         cmailResult[i] = CMAIL_NOT_RESULT;
13152     }
13153     nCmailResults = 0;
13154
13155     if (inFilename == NULL) {
13156         /* Because the filenames are static they only get malloced once  */
13157         /* and they never get freed                                      */
13158         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13159         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13160
13161         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13162         sprintf(outFilename, "%s.out", appData.cmailGameName);
13163     }
13164
13165     status = stat(outFilename, &outbuf);
13166     if (status < 0) {
13167         cmailMailedMove = FALSE;
13168     } else {
13169         status = stat(inFilename, &inbuf);
13170         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13171     }
13172
13173     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13174        counts the games, notes how each one terminated, etc.
13175
13176        It would be nice to remove this kludge and instead gather all
13177        the information while building the game list.  (And to keep it
13178        in the game list nodes instead of having a bunch of fixed-size
13179        parallel arrays.)  Note this will require getting each game's
13180        termination from the PGN tags, as the game list builder does
13181        not process the game moves.  --mann
13182        */
13183     cmailMsgLoaded = TRUE;
13184     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13185
13186     /* Load first game in the file or popup game menu */
13187     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13188
13189 #endif /* !WIN32 */
13190     return;
13191 }
13192
13193 int
13194 RegisterMove ()
13195 {
13196     FILE *f;
13197     char string[MSG_SIZ];
13198
13199     if (   cmailMailedMove
13200         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13201         return TRUE;            /* Allow free viewing  */
13202     }
13203
13204     /* Unregister move to ensure that we don't leave RegisterMove        */
13205     /* with the move registered when the conditions for registering no   */
13206     /* longer hold                                                       */
13207     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13208         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13209         nCmailMovesRegistered --;
13210
13211         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13212           {
13213               free(cmailCommentList[lastLoadGameNumber - 1]);
13214               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13215           }
13216     }
13217
13218     if (cmailOldMove == -1) {
13219         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13220         return FALSE;
13221     }
13222
13223     if (currentMove > cmailOldMove + 1) {
13224         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13225         return FALSE;
13226     }
13227
13228     if (currentMove < cmailOldMove) {
13229         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13230         return FALSE;
13231     }
13232
13233     if (forwardMostMove > currentMove) {
13234         /* Silently truncate extra moves */
13235         TruncateGame();
13236     }
13237
13238     if (   (currentMove == cmailOldMove + 1)
13239         || (   (currentMove == cmailOldMove)
13240             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13241                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13242         if (gameInfo.result != GameUnfinished) {
13243             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13244         }
13245
13246         if (commentList[currentMove] != NULL) {
13247             cmailCommentList[lastLoadGameNumber - 1]
13248               = StrSave(commentList[currentMove]);
13249         }
13250         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13251
13252         if (appData.debugMode)
13253           fprintf(debugFP, "Saving %s for game %d\n",
13254                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13255
13256         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13257
13258         f = fopen(string, "w");
13259         if (appData.oldSaveStyle) {
13260             SaveGameOldStyle(f); /* also closes the file */
13261
13262             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13263             f = fopen(string, "w");
13264             SavePosition(f, 0, NULL); /* also closes the file */
13265         } else {
13266             fprintf(f, "{--------------\n");
13267             PrintPosition(f, currentMove);
13268             fprintf(f, "--------------}\n\n");
13269
13270             SaveGame(f, 0, NULL); /* also closes the file*/
13271         }
13272
13273         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13274         nCmailMovesRegistered ++;
13275     } else if (nCmailGames == 1) {
13276         DisplayError(_("You have not made a move yet"), 0);
13277         return FALSE;
13278     }
13279
13280     return TRUE;
13281 }
13282
13283 void
13284 MailMoveEvent ()
13285 {
13286 #if !WIN32
13287     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13288     FILE *commandOutput;
13289     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13290     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13291     int nBuffers;
13292     int i;
13293     int archived;
13294     char *arcDir;
13295
13296     if (! cmailMsgLoaded) {
13297         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13298         return;
13299     }
13300
13301     if (nCmailGames == nCmailResults) {
13302         DisplayError(_("No unfinished games"), 0);
13303         return;
13304     }
13305
13306 #if CMAIL_PROHIBIT_REMAIL
13307     if (cmailMailedMove) {
13308       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);
13309         DisplayError(msg, 0);
13310         return;
13311     }
13312 #endif
13313
13314     if (! (cmailMailedMove || RegisterMove())) return;
13315
13316     if (   cmailMailedMove
13317         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13318       snprintf(string, MSG_SIZ, partCommandString,
13319                appData.debugMode ? " -v" : "", appData.cmailGameName);
13320         commandOutput = popen(string, "r");
13321
13322         if (commandOutput == NULL) {
13323             DisplayError(_("Failed to invoke cmail"), 0);
13324         } else {
13325             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13326                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13327             }
13328             if (nBuffers > 1) {
13329                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13330                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13331                 nBytes = MSG_SIZ - 1;
13332             } else {
13333                 (void) memcpy(msg, buffer, nBytes);
13334             }
13335             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13336
13337             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13338                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13339
13340                 archived = TRUE;
13341                 for (i = 0; i < nCmailGames; i ++) {
13342                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13343                         archived = FALSE;
13344                     }
13345                 }
13346                 if (   archived
13347                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13348                         != NULL)) {
13349                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13350                            arcDir,
13351                            appData.cmailGameName,
13352                            gameInfo.date);
13353                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13354                     cmailMsgLoaded = FALSE;
13355                 }
13356             }
13357
13358             DisplayInformation(msg);
13359             pclose(commandOutput);
13360         }
13361     } else {
13362         if ((*cmailMsg) != '\0') {
13363             DisplayInformation(cmailMsg);
13364         }
13365     }
13366
13367     return;
13368 #endif /* !WIN32 */
13369 }
13370
13371 char *
13372 CmailMsg ()
13373 {
13374 #if WIN32
13375     return NULL;
13376 #else
13377     int  prependComma = 0;
13378     char number[5];
13379     char string[MSG_SIZ];       /* Space for game-list */
13380     int  i;
13381
13382     if (!cmailMsgLoaded) return "";
13383
13384     if (cmailMailedMove) {
13385       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13386     } else {
13387         /* Create a list of games left */
13388       snprintf(string, MSG_SIZ, "[");
13389         for (i = 0; i < nCmailGames; i ++) {
13390             if (! (   cmailMoveRegistered[i]
13391                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13392                 if (prependComma) {
13393                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13394                 } else {
13395                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13396                     prependComma = 1;
13397                 }
13398
13399                 strcat(string, number);
13400             }
13401         }
13402         strcat(string, "]");
13403
13404         if (nCmailMovesRegistered + nCmailResults == 0) {
13405             switch (nCmailGames) {
13406               case 1:
13407                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13408                 break;
13409
13410               case 2:
13411                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13412                 break;
13413
13414               default:
13415                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13416                          nCmailGames);
13417                 break;
13418             }
13419         } else {
13420             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13421               case 1:
13422                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13423                          string);
13424                 break;
13425
13426               case 0:
13427                 if (nCmailResults == nCmailGames) {
13428                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13429                 } else {
13430                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13431                 }
13432                 break;
13433
13434               default:
13435                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13436                          string);
13437             }
13438         }
13439     }
13440     return cmailMsg;
13441 #endif /* WIN32 */
13442 }
13443
13444 void
13445 ResetGameEvent ()
13446 {
13447     if (gameMode == Training)
13448       SetTrainingModeOff();
13449
13450     Reset(TRUE, TRUE);
13451     cmailMsgLoaded = FALSE;
13452     if (appData.icsActive) {
13453       SendToICS(ics_prefix);
13454       SendToICS("refresh\n");
13455     }
13456 }
13457
13458 void
13459 ExitEvent (int status)
13460 {
13461     exiting++;
13462     if (exiting > 2) {
13463       /* Give up on clean exit */
13464       exit(status);
13465     }
13466     if (exiting > 1) {
13467       /* Keep trying for clean exit */
13468       return;
13469     }
13470
13471     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13472
13473     if (telnetISR != NULL) {
13474       RemoveInputSource(telnetISR);
13475     }
13476     if (icsPR != NoProc) {
13477       DestroyChildProcess(icsPR, TRUE);
13478     }
13479
13480     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13481     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13482
13483     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13484     /* make sure this other one finishes before killing it!                  */
13485     if(endingGame) { int count = 0;
13486         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13487         while(endingGame && count++ < 10) DoSleep(1);
13488         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13489     }
13490
13491     /* Kill off chess programs */
13492     if (first.pr != NoProc) {
13493         ExitAnalyzeMode();
13494
13495         DoSleep( appData.delayBeforeQuit );
13496         SendToProgram("quit\n", &first);
13497         DoSleep( appData.delayAfterQuit );
13498         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13499     }
13500     if (second.pr != NoProc) {
13501         DoSleep( appData.delayBeforeQuit );
13502         SendToProgram("quit\n", &second);
13503         DoSleep( appData.delayAfterQuit );
13504         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13505     }
13506     if (first.isr != NULL) {
13507         RemoveInputSource(first.isr);
13508     }
13509     if (second.isr != NULL) {
13510         RemoveInputSource(second.isr);
13511     }
13512
13513     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13514     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13515
13516     ShutDownFrontEnd();
13517     exit(status);
13518 }
13519
13520 void
13521 PauseEngine (ChessProgramState *cps)
13522 {
13523     SendToProgram("pause\n", cps);
13524     cps->pause = 2;
13525 }
13526
13527 void
13528 UnPauseEngine (ChessProgramState *cps)
13529 {
13530     SendToProgram("resume\n", cps);
13531     cps->pause = 1;
13532 }
13533
13534 void
13535 PauseEvent ()
13536 {
13537     if (appData.debugMode)
13538         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13539     if (pausing) {
13540         pausing = FALSE;
13541         ModeHighlight();
13542         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13543             StartClocks();
13544             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13545                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13546                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13547             }
13548             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13549             HandleMachineMove(stashedInputMove, stalledEngine);
13550             stalledEngine = NULL;
13551             return;
13552         }
13553         if (gameMode == MachinePlaysWhite ||
13554             gameMode == TwoMachinesPlay   ||
13555             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13556             if(first.pause)  UnPauseEngine(&first);
13557             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13558             if(second.pause) UnPauseEngine(&second);
13559             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13560             StartClocks();
13561         } else {
13562             DisplayBothClocks();
13563         }
13564         if (gameMode == PlayFromGameFile) {
13565             if (appData.timeDelay >= 0)
13566                 AutoPlayGameLoop();
13567         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13568             Reset(FALSE, TRUE);
13569             SendToICS(ics_prefix);
13570             SendToICS("refresh\n");
13571         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13572             ForwardInner(forwardMostMove);
13573         }
13574         pauseExamInvalid = FALSE;
13575     } else {
13576         switch (gameMode) {
13577           default:
13578             return;
13579           case IcsExamining:
13580             pauseExamForwardMostMove = forwardMostMove;
13581             pauseExamInvalid = FALSE;
13582             /* fall through */
13583           case IcsObserving:
13584           case IcsPlayingWhite:
13585           case IcsPlayingBlack:
13586             pausing = TRUE;
13587             ModeHighlight();
13588             return;
13589           case PlayFromGameFile:
13590             (void) StopLoadGameTimer();
13591             pausing = TRUE;
13592             ModeHighlight();
13593             break;
13594           case BeginningOfGame:
13595             if (appData.icsActive) return;
13596             /* else fall through */
13597           case MachinePlaysWhite:
13598           case MachinePlaysBlack:
13599           case TwoMachinesPlay:
13600             if (forwardMostMove == 0)
13601               return;           /* don't pause if no one has moved */
13602             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13603                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13604                 if(onMove->pause) {           // thinking engine can be paused
13605                     PauseEngine(onMove);      // do it
13606                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13607                         PauseEngine(onMove->other);
13608                     else
13609                         SendToProgram("easy\n", onMove->other);
13610                     StopClocks();
13611                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13612             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13613                 if(first.pause) {
13614                     PauseEngine(&first);
13615                     StopClocks();
13616                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13617             } else { // human on move, pause pondering by either method
13618                 if(first.pause)
13619                     PauseEngine(&first);
13620                 else if(appData.ponderNextMove)
13621                     SendToProgram("easy\n", &first);
13622                 StopClocks();
13623             }
13624             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13625           case AnalyzeMode:
13626             pausing = TRUE;
13627             ModeHighlight();
13628             break;
13629         }
13630     }
13631 }
13632
13633 void
13634 EditCommentEvent ()
13635 {
13636     char title[MSG_SIZ];
13637
13638     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13639       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13640     } else {
13641       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13642                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13643                parseList[currentMove - 1]);
13644     }
13645
13646     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13647 }
13648
13649
13650 void
13651 EditTagsEvent ()
13652 {
13653     char *tags = PGNTags(&gameInfo);
13654     bookUp = FALSE;
13655     EditTagsPopUp(tags, NULL);
13656     free(tags);
13657 }
13658
13659 void
13660 ToggleSecond ()
13661 {
13662   if(second.analyzing) {
13663     SendToProgram("exit\n", &second);
13664     second.analyzing = FALSE;
13665   } else {
13666     if (second.pr == NoProc) StartChessProgram(&second);
13667     InitChessProgram(&second, FALSE);
13668     FeedMovesToProgram(&second, currentMove);
13669
13670     SendToProgram("analyze\n", &second);
13671     second.analyzing = TRUE;
13672   }
13673 }
13674
13675 /* Toggle ShowThinking */
13676 void
13677 ToggleShowThinking()
13678 {
13679   appData.showThinking = !appData.showThinking;
13680   ShowThinkingEvent();
13681 }
13682
13683 int
13684 AnalyzeModeEvent ()
13685 {
13686     char buf[MSG_SIZ];
13687
13688     if (!first.analysisSupport) {
13689       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13690       DisplayError(buf, 0);
13691       return 0;
13692     }
13693     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13694     if (appData.icsActive) {
13695         if (gameMode != IcsObserving) {
13696           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13697             DisplayError(buf, 0);
13698             /* secure check */
13699             if (appData.icsEngineAnalyze) {
13700                 if (appData.debugMode)
13701                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13702                 ExitAnalyzeMode();
13703                 ModeHighlight();
13704             }
13705             return 0;
13706         }
13707         /* if enable, user wants to disable icsEngineAnalyze */
13708         if (appData.icsEngineAnalyze) {
13709                 ExitAnalyzeMode();
13710                 ModeHighlight();
13711                 return 0;
13712         }
13713         appData.icsEngineAnalyze = TRUE;
13714         if (appData.debugMode)
13715             fprintf(debugFP, "ICS engine analyze starting... \n");
13716     }
13717
13718     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13719     if (appData.noChessProgram || gameMode == AnalyzeMode)
13720       return 0;
13721
13722     if (gameMode != AnalyzeFile) {
13723         if (!appData.icsEngineAnalyze) {
13724                EditGameEvent();
13725                if (gameMode != EditGame) return 0;
13726         }
13727         if (!appData.showThinking) ToggleShowThinking();
13728         ResurrectChessProgram();
13729         SendToProgram("analyze\n", &first);
13730         first.analyzing = TRUE;
13731         /*first.maybeThinking = TRUE;*/
13732         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13733         EngineOutputPopUp();
13734     }
13735     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13736     pausing = FALSE;
13737     ModeHighlight();
13738     SetGameInfo();
13739
13740     StartAnalysisClock();
13741     GetTimeMark(&lastNodeCountTime);
13742     lastNodeCount = 0;
13743     return 1;
13744 }
13745
13746 void
13747 AnalyzeFileEvent ()
13748 {
13749     if (appData.noChessProgram || gameMode == AnalyzeFile)
13750       return;
13751
13752     if (!first.analysisSupport) {
13753       char buf[MSG_SIZ];
13754       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13755       DisplayError(buf, 0);
13756       return;
13757     }
13758
13759     if (gameMode != AnalyzeMode) {
13760         keepInfo = 1; // mere annotating should not alter PGN tags
13761         EditGameEvent();
13762         keepInfo = 0;
13763         if (gameMode != EditGame) return;
13764         if (!appData.showThinking) ToggleShowThinking();
13765         ResurrectChessProgram();
13766         SendToProgram("analyze\n", &first);
13767         first.analyzing = TRUE;
13768         /*first.maybeThinking = TRUE;*/
13769         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13770         EngineOutputPopUp();
13771     }
13772     gameMode = AnalyzeFile;
13773     pausing = FALSE;
13774     ModeHighlight();
13775
13776     StartAnalysisClock();
13777     GetTimeMark(&lastNodeCountTime);
13778     lastNodeCount = 0;
13779     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13780     AnalysisPeriodicEvent(1);
13781 }
13782
13783 void
13784 MachineWhiteEvent ()
13785 {
13786     char buf[MSG_SIZ];
13787     char *bookHit = NULL;
13788
13789     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13790       return;
13791
13792
13793     if (gameMode == PlayFromGameFile ||
13794         gameMode == TwoMachinesPlay  ||
13795         gameMode == Training         ||
13796         gameMode == AnalyzeMode      ||
13797         gameMode == EndOfGame)
13798         EditGameEvent();
13799
13800     if (gameMode == EditPosition)
13801         EditPositionDone(TRUE);
13802
13803     if (!WhiteOnMove(currentMove)) {
13804         DisplayError(_("It is not White's turn"), 0);
13805         return;
13806     }
13807
13808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13809       ExitAnalyzeMode();
13810
13811     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13812         gameMode == AnalyzeFile)
13813         TruncateGame();
13814
13815     ResurrectChessProgram();    /* in case it isn't running */
13816     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13817         gameMode = MachinePlaysWhite;
13818         ResetClocks();
13819     } else
13820     gameMode = MachinePlaysWhite;
13821     pausing = FALSE;
13822     ModeHighlight();
13823     SetGameInfo();
13824     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13825     DisplayTitle(buf);
13826     if (first.sendName) {
13827       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13828       SendToProgram(buf, &first);
13829     }
13830     if (first.sendTime) {
13831       if (first.useColors) {
13832         SendToProgram("black\n", &first); /*gnu kludge*/
13833       }
13834       SendTimeRemaining(&first, TRUE);
13835     }
13836     if (first.useColors) {
13837       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13838     }
13839     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13840     SetMachineThinkingEnables();
13841     first.maybeThinking = TRUE;
13842     StartClocks();
13843     firstMove = FALSE;
13844
13845     if (appData.autoFlipView && !flipView) {
13846       flipView = !flipView;
13847       DrawPosition(FALSE, NULL);
13848       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13849     }
13850
13851     if(bookHit) { // [HGM] book: simulate book reply
13852         static char bookMove[MSG_SIZ]; // a bit generous?
13853
13854         programStats.nodes = programStats.depth = programStats.time =
13855         programStats.score = programStats.got_only_move = 0;
13856         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13857
13858         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13859         strcat(bookMove, bookHit);
13860         HandleMachineMove(bookMove, &first);
13861     }
13862 }
13863
13864 void
13865 MachineBlackEvent ()
13866 {
13867   char buf[MSG_SIZ];
13868   char *bookHit = NULL;
13869
13870     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13871         return;
13872
13873
13874     if (gameMode == PlayFromGameFile ||
13875         gameMode == TwoMachinesPlay  ||
13876         gameMode == Training         ||
13877         gameMode == AnalyzeMode      ||
13878         gameMode == EndOfGame)
13879         EditGameEvent();
13880
13881     if (gameMode == EditPosition)
13882         EditPositionDone(TRUE);
13883
13884     if (WhiteOnMove(currentMove)) {
13885         DisplayError(_("It is not Black's turn"), 0);
13886         return;
13887     }
13888
13889     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13890       ExitAnalyzeMode();
13891
13892     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13893         gameMode == AnalyzeFile)
13894         TruncateGame();
13895
13896     ResurrectChessProgram();    /* in case it isn't running */
13897     gameMode = MachinePlaysBlack;
13898     pausing = FALSE;
13899     ModeHighlight();
13900     SetGameInfo();
13901     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13902     DisplayTitle(buf);
13903     if (first.sendName) {
13904       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13905       SendToProgram(buf, &first);
13906     }
13907     if (first.sendTime) {
13908       if (first.useColors) {
13909         SendToProgram("white\n", &first); /*gnu kludge*/
13910       }
13911       SendTimeRemaining(&first, FALSE);
13912     }
13913     if (first.useColors) {
13914       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13915     }
13916     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13917     SetMachineThinkingEnables();
13918     first.maybeThinking = TRUE;
13919     StartClocks();
13920
13921     if (appData.autoFlipView && flipView) {
13922       flipView = !flipView;
13923       DrawPosition(FALSE, NULL);
13924       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13925     }
13926     if(bookHit) { // [HGM] book: simulate book reply
13927         static char bookMove[MSG_SIZ]; // a bit generous?
13928
13929         programStats.nodes = programStats.depth = programStats.time =
13930         programStats.score = programStats.got_only_move = 0;
13931         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13932
13933         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13934         strcat(bookMove, bookHit);
13935         HandleMachineMove(bookMove, &first);
13936     }
13937 }
13938
13939
13940 void
13941 DisplayTwoMachinesTitle ()
13942 {
13943     char buf[MSG_SIZ];
13944     if (appData.matchGames > 0) {
13945         if(appData.tourneyFile[0]) {
13946           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13947                    gameInfo.white, _("vs."), gameInfo.black,
13948                    nextGame+1, appData.matchGames+1,
13949                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13950         } else
13951         if (first.twoMachinesColor[0] == 'w') {
13952           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13953                    gameInfo.white, _("vs."),  gameInfo.black,
13954                    first.matchWins, second.matchWins,
13955                    matchGame - 1 - (first.matchWins + second.matchWins));
13956         } else {
13957           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13958                    gameInfo.white, _("vs."), gameInfo.black,
13959                    second.matchWins, first.matchWins,
13960                    matchGame - 1 - (first.matchWins + second.matchWins));
13961         }
13962     } else {
13963       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13964     }
13965     DisplayTitle(buf);
13966 }
13967
13968 void
13969 SettingsMenuIfReady ()
13970 {
13971   if (second.lastPing != second.lastPong) {
13972     DisplayMessage("", _("Waiting for second chess program"));
13973     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13974     return;
13975   }
13976   ThawUI();
13977   DisplayMessage("", "");
13978   SettingsPopUp(&second);
13979 }
13980
13981 int
13982 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13983 {
13984     char buf[MSG_SIZ];
13985     if (cps->pr == NoProc) {
13986         StartChessProgram(cps);
13987         if (cps->protocolVersion == 1) {
13988           retry();
13989           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13990         } else {
13991           /* kludge: allow timeout for initial "feature" command */
13992           if(retry != TwoMachinesEventIfReady) FreezeUI();
13993           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13994           DisplayMessage("", buf);
13995           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13996         }
13997         return 1;
13998     }
13999     return 0;
14000 }
14001
14002 void
14003 TwoMachinesEvent P((void))
14004 {
14005     int i;
14006     char buf[MSG_SIZ];
14007     ChessProgramState *onmove;
14008     char *bookHit = NULL;
14009     static int stalling = 0;
14010     TimeMark now;
14011     long wait;
14012
14013     if (appData.noChessProgram) return;
14014
14015     switch (gameMode) {
14016       case TwoMachinesPlay:
14017         return;
14018       case MachinePlaysWhite:
14019       case MachinePlaysBlack:
14020         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14021             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14022             return;
14023         }
14024         /* fall through */
14025       case BeginningOfGame:
14026       case PlayFromGameFile:
14027       case EndOfGame:
14028         EditGameEvent();
14029         if (gameMode != EditGame) return;
14030         break;
14031       case EditPosition:
14032         EditPositionDone(TRUE);
14033         break;
14034       case AnalyzeMode:
14035       case AnalyzeFile:
14036         ExitAnalyzeMode();
14037         break;
14038       case EditGame:
14039       default:
14040         break;
14041     }
14042
14043 //    forwardMostMove = currentMove;
14044     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14045     startingEngine = TRUE;
14046
14047     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14048
14049     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14050     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14051       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14052       return;
14053     }
14054     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14055
14056     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14057         startingEngine = FALSE;
14058         DisplayError("second engine does not play this", 0);
14059         return;
14060     }
14061
14062     if(!stalling) {
14063       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14064       SendToProgram("force\n", &second);
14065       stalling = 1;
14066       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14067       return;
14068     }
14069     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14070     if(appData.matchPause>10000 || appData.matchPause<10)
14071                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14072     wait = SubtractTimeMarks(&now, &pauseStart);
14073     if(wait < appData.matchPause) {
14074         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14075         return;
14076     }
14077     // we are now committed to starting the game
14078     stalling = 0;
14079     DisplayMessage("", "");
14080     if (startedFromSetupPosition) {
14081         SendBoard(&second, backwardMostMove);
14082     if (appData.debugMode) {
14083         fprintf(debugFP, "Two Machines\n");
14084     }
14085     }
14086     for (i = backwardMostMove; i < forwardMostMove; i++) {
14087         SendMoveToProgram(i, &second);
14088     }
14089
14090     gameMode = TwoMachinesPlay;
14091     pausing = startingEngine = FALSE;
14092     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14093     SetGameInfo();
14094     DisplayTwoMachinesTitle();
14095     firstMove = TRUE;
14096     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14097         onmove = &first;
14098     } else {
14099         onmove = &second;
14100     }
14101     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14102     SendToProgram(first.computerString, &first);
14103     if (first.sendName) {
14104       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14105       SendToProgram(buf, &first);
14106     }
14107     SendToProgram(second.computerString, &second);
14108     if (second.sendName) {
14109       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14110       SendToProgram(buf, &second);
14111     }
14112
14113     ResetClocks();
14114     if (!first.sendTime || !second.sendTime) {
14115         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14116         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14117     }
14118     if (onmove->sendTime) {
14119       if (onmove->useColors) {
14120         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14121       }
14122       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14123     }
14124     if (onmove->useColors) {
14125       SendToProgram(onmove->twoMachinesColor, onmove);
14126     }
14127     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14128 //    SendToProgram("go\n", onmove);
14129     onmove->maybeThinking = TRUE;
14130     SetMachineThinkingEnables();
14131
14132     StartClocks();
14133
14134     if(bookHit) { // [HGM] book: simulate book reply
14135         static char bookMove[MSG_SIZ]; // a bit generous?
14136
14137         programStats.nodes = programStats.depth = programStats.time =
14138         programStats.score = programStats.got_only_move = 0;
14139         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14140
14141         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14142         strcat(bookMove, bookHit);
14143         savedMessage = bookMove; // args for deferred call
14144         savedState = onmove;
14145         ScheduleDelayedEvent(DeferredBookMove, 1);
14146     }
14147 }
14148
14149 void
14150 TrainingEvent ()
14151 {
14152     if (gameMode == Training) {
14153       SetTrainingModeOff();
14154       gameMode = PlayFromGameFile;
14155       DisplayMessage("", _("Training mode off"));
14156     } else {
14157       gameMode = Training;
14158       animateTraining = appData.animate;
14159
14160       /* make sure we are not already at the end of the game */
14161       if (currentMove < forwardMostMove) {
14162         SetTrainingModeOn();
14163         DisplayMessage("", _("Training mode on"));
14164       } else {
14165         gameMode = PlayFromGameFile;
14166         DisplayError(_("Already at end of game"), 0);
14167       }
14168     }
14169     ModeHighlight();
14170 }
14171
14172 void
14173 IcsClientEvent ()
14174 {
14175     if (!appData.icsActive) return;
14176     switch (gameMode) {
14177       case IcsPlayingWhite:
14178       case IcsPlayingBlack:
14179       case IcsObserving:
14180       case IcsIdle:
14181       case BeginningOfGame:
14182       case IcsExamining:
14183         return;
14184
14185       case EditGame:
14186         break;
14187
14188       case EditPosition:
14189         EditPositionDone(TRUE);
14190         break;
14191
14192       case AnalyzeMode:
14193       case AnalyzeFile:
14194         ExitAnalyzeMode();
14195         break;
14196
14197       default:
14198         EditGameEvent();
14199         break;
14200     }
14201
14202     gameMode = IcsIdle;
14203     ModeHighlight();
14204     return;
14205 }
14206
14207 void
14208 EditGameEvent ()
14209 {
14210     int i;
14211
14212     switch (gameMode) {
14213       case Training:
14214         SetTrainingModeOff();
14215         break;
14216       case MachinePlaysWhite:
14217       case MachinePlaysBlack:
14218       case BeginningOfGame:
14219         SendToProgram("force\n", &first);
14220         SetUserThinkingEnables();
14221         break;
14222       case PlayFromGameFile:
14223         (void) StopLoadGameTimer();
14224         if (gameFileFP != NULL) {
14225             gameFileFP = NULL;
14226         }
14227         break;
14228       case EditPosition:
14229         EditPositionDone(TRUE);
14230         break;
14231       case AnalyzeMode:
14232       case AnalyzeFile:
14233         ExitAnalyzeMode();
14234         SendToProgram("force\n", &first);
14235         break;
14236       case TwoMachinesPlay:
14237         GameEnds(EndOfFile, NULL, GE_PLAYER);
14238         ResurrectChessProgram();
14239         SetUserThinkingEnables();
14240         break;
14241       case EndOfGame:
14242         ResurrectChessProgram();
14243         break;
14244       case IcsPlayingBlack:
14245       case IcsPlayingWhite:
14246         DisplayError(_("Warning: You are still playing a game"), 0);
14247         break;
14248       case IcsObserving:
14249         DisplayError(_("Warning: You are still observing a game"), 0);
14250         break;
14251       case IcsExamining:
14252         DisplayError(_("Warning: You are still examining a game"), 0);
14253         break;
14254       case IcsIdle:
14255         break;
14256       case EditGame:
14257       default:
14258         return;
14259     }
14260
14261     pausing = FALSE;
14262     StopClocks();
14263     first.offeredDraw = second.offeredDraw = 0;
14264
14265     if (gameMode == PlayFromGameFile) {
14266         whiteTimeRemaining = timeRemaining[0][currentMove];
14267         blackTimeRemaining = timeRemaining[1][currentMove];
14268         DisplayTitle("");
14269     }
14270
14271     if (gameMode == MachinePlaysWhite ||
14272         gameMode == MachinePlaysBlack ||
14273         gameMode == TwoMachinesPlay ||
14274         gameMode == EndOfGame) {
14275         i = forwardMostMove;
14276         while (i > currentMove) {
14277             SendToProgram("undo\n", &first);
14278             i--;
14279         }
14280         if(!adjustedClock) {
14281         whiteTimeRemaining = timeRemaining[0][currentMove];
14282         blackTimeRemaining = timeRemaining[1][currentMove];
14283         DisplayBothClocks();
14284         }
14285         if (whiteFlag || blackFlag) {
14286             whiteFlag = blackFlag = 0;
14287         }
14288         DisplayTitle("");
14289     }
14290
14291     gameMode = EditGame;
14292     ModeHighlight();
14293     SetGameInfo();
14294 }
14295
14296
14297 void
14298 EditPositionEvent ()
14299 {
14300     if (gameMode == EditPosition) {
14301         EditGameEvent();
14302         return;
14303     }
14304
14305     EditGameEvent();
14306     if (gameMode != EditGame) return;
14307
14308     gameMode = EditPosition;
14309     ModeHighlight();
14310     SetGameInfo();
14311     if (currentMove > 0)
14312       CopyBoard(boards[0], boards[currentMove]);
14313
14314     blackPlaysFirst = !WhiteOnMove(currentMove);
14315     ResetClocks();
14316     currentMove = forwardMostMove = backwardMostMove = 0;
14317     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14318     DisplayMove(-1);
14319     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14320 }
14321
14322 void
14323 ExitAnalyzeMode ()
14324 {
14325     /* [DM] icsEngineAnalyze - possible call from other functions */
14326     if (appData.icsEngineAnalyze) {
14327         appData.icsEngineAnalyze = FALSE;
14328
14329         DisplayMessage("",_("Close ICS engine analyze..."));
14330     }
14331     if (first.analysisSupport && first.analyzing) {
14332       SendToBoth("exit\n");
14333       first.analyzing = second.analyzing = FALSE;
14334     }
14335     thinkOutput[0] = NULLCHAR;
14336 }
14337
14338 void
14339 EditPositionDone (Boolean fakeRights)
14340 {
14341     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14342
14343     startedFromSetupPosition = TRUE;
14344     InitChessProgram(&first, FALSE);
14345     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14346       boards[0][EP_STATUS] = EP_NONE;
14347       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14348       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14349         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14350         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14351       } else boards[0][CASTLING][2] = NoRights;
14352       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14353         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14354         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14355       } else boards[0][CASTLING][5] = NoRights;
14356       if(gameInfo.variant == VariantSChess) {
14357         int i;
14358         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14359           boards[0][VIRGIN][i] = 0;
14360           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14361           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14362         }
14363       }
14364     }
14365     SendToProgram("force\n", &first);
14366     if (blackPlaysFirst) {
14367         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14368         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14369         currentMove = forwardMostMove = backwardMostMove = 1;
14370         CopyBoard(boards[1], boards[0]);
14371     } else {
14372         currentMove = forwardMostMove = backwardMostMove = 0;
14373     }
14374     SendBoard(&first, forwardMostMove);
14375     if (appData.debugMode) {
14376         fprintf(debugFP, "EditPosDone\n");
14377     }
14378     DisplayTitle("");
14379     DisplayMessage("", "");
14380     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14381     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14382     gameMode = EditGame;
14383     ModeHighlight();
14384     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14385     ClearHighlights(); /* [AS] */
14386 }
14387
14388 /* Pause for `ms' milliseconds */
14389 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14390 void
14391 TimeDelay (long ms)
14392 {
14393     TimeMark m1, m2;
14394
14395     GetTimeMark(&m1);
14396     do {
14397         GetTimeMark(&m2);
14398     } while (SubtractTimeMarks(&m2, &m1) < ms);
14399 }
14400
14401 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14402 void
14403 SendMultiLineToICS (char *buf)
14404 {
14405     char temp[MSG_SIZ+1], *p;
14406     int len;
14407
14408     len = strlen(buf);
14409     if (len > MSG_SIZ)
14410       len = MSG_SIZ;
14411
14412     strncpy(temp, buf, len);
14413     temp[len] = 0;
14414
14415     p = temp;
14416     while (*p) {
14417         if (*p == '\n' || *p == '\r')
14418           *p = ' ';
14419         ++p;
14420     }
14421
14422     strcat(temp, "\n");
14423     SendToICS(temp);
14424     SendToPlayer(temp, strlen(temp));
14425 }
14426
14427 void
14428 SetWhiteToPlayEvent ()
14429 {
14430     if (gameMode == EditPosition) {
14431         blackPlaysFirst = FALSE;
14432         DisplayBothClocks();    /* works because currentMove is 0 */
14433     } else if (gameMode == IcsExamining) {
14434         SendToICS(ics_prefix);
14435         SendToICS("tomove white\n");
14436     }
14437 }
14438
14439 void
14440 SetBlackToPlayEvent ()
14441 {
14442     if (gameMode == EditPosition) {
14443         blackPlaysFirst = TRUE;
14444         currentMove = 1;        /* kludge */
14445         DisplayBothClocks();
14446         currentMove = 0;
14447     } else if (gameMode == IcsExamining) {
14448         SendToICS(ics_prefix);
14449         SendToICS("tomove black\n");
14450     }
14451 }
14452
14453 void
14454 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14455 {
14456     char buf[MSG_SIZ];
14457     ChessSquare piece = boards[0][y][x];
14458
14459     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14460
14461     switch (selection) {
14462       case ClearBoard:
14463         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14464             SendToICS(ics_prefix);
14465             SendToICS("bsetup clear\n");
14466         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14467             SendToICS(ics_prefix);
14468             SendToICS("clearboard\n");
14469         } else {
14470             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14471                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14472                 for (y = 0; y < BOARD_HEIGHT; y++) {
14473                     if (gameMode == IcsExamining) {
14474                         if (boards[currentMove][y][x] != EmptySquare) {
14475                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14476                                     AAA + x, ONE + y);
14477                             SendToICS(buf);
14478                         }
14479                     } else {
14480                         boards[0][y][x] = p;
14481                     }
14482                 }
14483             }
14484         }
14485         if (gameMode == EditPosition) {
14486             DrawPosition(FALSE, boards[0]);
14487         }
14488         break;
14489
14490       case WhitePlay:
14491         SetWhiteToPlayEvent();
14492         break;
14493
14494       case BlackPlay:
14495         SetBlackToPlayEvent();
14496         break;
14497
14498       case EmptySquare:
14499         if (gameMode == IcsExamining) {
14500             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14501             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14502             SendToICS(buf);
14503         } else {
14504             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14505                 if(x == BOARD_LEFT-2) {
14506                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14507                     boards[0][y][1] = 0;
14508                 } else
14509                 if(x == BOARD_RGHT+1) {
14510                     if(y >= gameInfo.holdingsSize) break;
14511                     boards[0][y][BOARD_WIDTH-2] = 0;
14512                 } else break;
14513             }
14514             boards[0][y][x] = EmptySquare;
14515             DrawPosition(FALSE, boards[0]);
14516         }
14517         break;
14518
14519       case PromotePiece:
14520         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14521            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14522             selection = (ChessSquare) (PROMOTED piece);
14523         } else if(piece == EmptySquare) selection = WhiteSilver;
14524         else selection = (ChessSquare)((int)piece - 1);
14525         goto defaultlabel;
14526
14527       case DemotePiece:
14528         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14529            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14530             selection = (ChessSquare) (DEMOTED piece);
14531         } else if(piece == EmptySquare) selection = BlackSilver;
14532         else selection = (ChessSquare)((int)piece + 1);
14533         goto defaultlabel;
14534
14535       case WhiteQueen:
14536       case BlackQueen:
14537         if(gameInfo.variant == VariantShatranj ||
14538            gameInfo.variant == VariantXiangqi  ||
14539            gameInfo.variant == VariantCourier  ||
14540            gameInfo.variant == VariantASEAN    ||
14541            gameInfo.variant == VariantMakruk     )
14542             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14543         goto defaultlabel;
14544
14545       case WhiteKing:
14546       case BlackKing:
14547         if(gameInfo.variant == VariantXiangqi)
14548             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14549         if(gameInfo.variant == VariantKnightmate)
14550             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14551       default:
14552         defaultlabel:
14553         if (gameMode == IcsExamining) {
14554             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14555             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14556                      PieceToChar(selection), AAA + x, ONE + y);
14557             SendToICS(buf);
14558         } else {
14559             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14560                 int n;
14561                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14562                     n = PieceToNumber(selection - BlackPawn);
14563                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14564                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14565                     boards[0][BOARD_HEIGHT-1-n][1]++;
14566                 } else
14567                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14568                     n = PieceToNumber(selection);
14569                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14570                     boards[0][n][BOARD_WIDTH-1] = selection;
14571                     boards[0][n][BOARD_WIDTH-2]++;
14572                 }
14573             } else
14574             boards[0][y][x] = selection;
14575             DrawPosition(TRUE, boards[0]);
14576             ClearHighlights();
14577             fromX = fromY = -1;
14578         }
14579         break;
14580     }
14581 }
14582
14583
14584 void
14585 DropMenuEvent (ChessSquare selection, int x, int y)
14586 {
14587     ChessMove moveType;
14588
14589     switch (gameMode) {
14590       case IcsPlayingWhite:
14591       case MachinePlaysBlack:
14592         if (!WhiteOnMove(currentMove)) {
14593             DisplayMoveError(_("It is Black's turn"));
14594             return;
14595         }
14596         moveType = WhiteDrop;
14597         break;
14598       case IcsPlayingBlack:
14599       case MachinePlaysWhite:
14600         if (WhiteOnMove(currentMove)) {
14601             DisplayMoveError(_("It is White's turn"));
14602             return;
14603         }
14604         moveType = BlackDrop;
14605         break;
14606       case EditGame:
14607         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14608         break;
14609       default:
14610         return;
14611     }
14612
14613     if (moveType == BlackDrop && selection < BlackPawn) {
14614       selection = (ChessSquare) ((int) selection
14615                                  + (int) BlackPawn - (int) WhitePawn);
14616     }
14617     if (boards[currentMove][y][x] != EmptySquare) {
14618         DisplayMoveError(_("That square is occupied"));
14619         return;
14620     }
14621
14622     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14623 }
14624
14625 void
14626 AcceptEvent ()
14627 {
14628     /* Accept a pending offer of any kind from opponent */
14629
14630     if (appData.icsActive) {
14631         SendToICS(ics_prefix);
14632         SendToICS("accept\n");
14633     } else if (cmailMsgLoaded) {
14634         if (currentMove == cmailOldMove &&
14635             commentList[cmailOldMove] != NULL &&
14636             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14637                    "Black offers a draw" : "White offers a draw")) {
14638             TruncateGame();
14639             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14640             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14641         } else {
14642             DisplayError(_("There is no pending offer on this move"), 0);
14643             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14644         }
14645     } else {
14646         /* Not used for offers from chess program */
14647     }
14648 }
14649
14650 void
14651 DeclineEvent ()
14652 {
14653     /* Decline a pending offer of any kind from opponent */
14654
14655     if (appData.icsActive) {
14656         SendToICS(ics_prefix);
14657         SendToICS("decline\n");
14658     } else if (cmailMsgLoaded) {
14659         if (currentMove == cmailOldMove &&
14660             commentList[cmailOldMove] != NULL &&
14661             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14662                    "Black offers a draw" : "White offers a draw")) {
14663 #ifdef NOTDEF
14664             AppendComment(cmailOldMove, "Draw declined", TRUE);
14665             DisplayComment(cmailOldMove - 1, "Draw declined");
14666 #endif /*NOTDEF*/
14667         } else {
14668             DisplayError(_("There is no pending offer on this move"), 0);
14669         }
14670     } else {
14671         /* Not used for offers from chess program */
14672     }
14673 }
14674
14675 void
14676 RematchEvent ()
14677 {
14678     /* Issue ICS rematch command */
14679     if (appData.icsActive) {
14680         SendToICS(ics_prefix);
14681         SendToICS("rematch\n");
14682     }
14683 }
14684
14685 void
14686 CallFlagEvent ()
14687 {
14688     /* Call your opponent's flag (claim a win on time) */
14689     if (appData.icsActive) {
14690         SendToICS(ics_prefix);
14691         SendToICS("flag\n");
14692     } else {
14693         switch (gameMode) {
14694           default:
14695             return;
14696           case MachinePlaysWhite:
14697             if (whiteFlag) {
14698                 if (blackFlag)
14699                   GameEnds(GameIsDrawn, "Both players ran out of time",
14700                            GE_PLAYER);
14701                 else
14702                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14703             } else {
14704                 DisplayError(_("Your opponent is not out of time"), 0);
14705             }
14706             break;
14707           case MachinePlaysBlack:
14708             if (blackFlag) {
14709                 if (whiteFlag)
14710                   GameEnds(GameIsDrawn, "Both players ran out of time",
14711                            GE_PLAYER);
14712                 else
14713                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14714             } else {
14715                 DisplayError(_("Your opponent is not out of time"), 0);
14716             }
14717             break;
14718         }
14719     }
14720 }
14721
14722 void
14723 ClockClick (int which)
14724 {       // [HGM] code moved to back-end from winboard.c
14725         if(which) { // black clock
14726           if (gameMode == EditPosition || gameMode == IcsExamining) {
14727             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14728             SetBlackToPlayEvent();
14729           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14730           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14731           } else if (shiftKey) {
14732             AdjustClock(which, -1);
14733           } else if (gameMode == IcsPlayingWhite ||
14734                      gameMode == MachinePlaysBlack) {
14735             CallFlagEvent();
14736           }
14737         } else { // white clock
14738           if (gameMode == EditPosition || gameMode == IcsExamining) {
14739             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14740             SetWhiteToPlayEvent();
14741           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14742           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14743           } else if (shiftKey) {
14744             AdjustClock(which, -1);
14745           } else if (gameMode == IcsPlayingBlack ||
14746                    gameMode == MachinePlaysWhite) {
14747             CallFlagEvent();
14748           }
14749         }
14750 }
14751
14752 void
14753 DrawEvent ()
14754 {
14755     /* Offer draw or accept pending draw offer from opponent */
14756
14757     if (appData.icsActive) {
14758         /* Note: tournament rules require draw offers to be
14759            made after you make your move but before you punch
14760            your clock.  Currently ICS doesn't let you do that;
14761            instead, you immediately punch your clock after making
14762            a move, but you can offer a draw at any time. */
14763
14764         SendToICS(ics_prefix);
14765         SendToICS("draw\n");
14766         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14767     } else if (cmailMsgLoaded) {
14768         if (currentMove == cmailOldMove &&
14769             commentList[cmailOldMove] != NULL &&
14770             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14771                    "Black offers a draw" : "White offers a draw")) {
14772             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14773             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14774         } else if (currentMove == cmailOldMove + 1) {
14775             char *offer = WhiteOnMove(cmailOldMove) ?
14776               "White offers a draw" : "Black offers a draw";
14777             AppendComment(currentMove, offer, TRUE);
14778             DisplayComment(currentMove - 1, offer);
14779             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14780         } else {
14781             DisplayError(_("You must make your move before offering a draw"), 0);
14782             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14783         }
14784     } else if (first.offeredDraw) {
14785         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14786     } else {
14787         if (first.sendDrawOffers) {
14788             SendToProgram("draw\n", &first);
14789             userOfferedDraw = TRUE;
14790         }
14791     }
14792 }
14793
14794 void
14795 AdjournEvent ()
14796 {
14797     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14798
14799     if (appData.icsActive) {
14800         SendToICS(ics_prefix);
14801         SendToICS("adjourn\n");
14802     } else {
14803         /* Currently GNU Chess doesn't offer or accept Adjourns */
14804     }
14805 }
14806
14807
14808 void
14809 AbortEvent ()
14810 {
14811     /* Offer Abort or accept pending Abort offer from opponent */
14812
14813     if (appData.icsActive) {
14814         SendToICS(ics_prefix);
14815         SendToICS("abort\n");
14816     } else {
14817         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14818     }
14819 }
14820
14821 void
14822 ResignEvent ()
14823 {
14824     /* Resign.  You can do this even if it's not your turn. */
14825
14826     if (appData.icsActive) {
14827         SendToICS(ics_prefix);
14828         SendToICS("resign\n");
14829     } else {
14830         switch (gameMode) {
14831           case MachinePlaysWhite:
14832             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14833             break;
14834           case MachinePlaysBlack:
14835             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14836             break;
14837           case EditGame:
14838             if (cmailMsgLoaded) {
14839                 TruncateGame();
14840                 if (WhiteOnMove(cmailOldMove)) {
14841                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14842                 } else {
14843                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14844                 }
14845                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14846             }
14847             break;
14848           default:
14849             break;
14850         }
14851     }
14852 }
14853
14854
14855 void
14856 StopObservingEvent ()
14857 {
14858     /* Stop observing current games */
14859     SendToICS(ics_prefix);
14860     SendToICS("unobserve\n");
14861 }
14862
14863 void
14864 StopExaminingEvent ()
14865 {
14866     /* Stop observing current game */
14867     SendToICS(ics_prefix);
14868     SendToICS("unexamine\n");
14869 }
14870
14871 void
14872 ForwardInner (int target)
14873 {
14874     int limit; int oldSeekGraphUp = seekGraphUp;
14875
14876     if (appData.debugMode)
14877         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14878                 target, currentMove, forwardMostMove);
14879
14880     if (gameMode == EditPosition)
14881       return;
14882
14883     seekGraphUp = FALSE;
14884     MarkTargetSquares(1);
14885
14886     if (gameMode == PlayFromGameFile && !pausing)
14887       PauseEvent();
14888
14889     if (gameMode == IcsExamining && pausing)
14890       limit = pauseExamForwardMostMove;
14891     else
14892       limit = forwardMostMove;
14893
14894     if (target > limit) target = limit;
14895
14896     if (target > 0 && moveList[target - 1][0]) {
14897         int fromX, fromY, toX, toY;
14898         toX = moveList[target - 1][2] - AAA;
14899         toY = moveList[target - 1][3] - ONE;
14900         if (moveList[target - 1][1] == '@') {
14901             if (appData.highlightLastMove) {
14902                 SetHighlights(-1, -1, toX, toY);
14903             }
14904         } else {
14905             fromX = moveList[target - 1][0] - AAA;
14906             fromY = moveList[target - 1][1] - ONE;
14907             if (target == currentMove + 1) {
14908                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14909             }
14910             if (appData.highlightLastMove) {
14911                 SetHighlights(fromX, fromY, toX, toY);
14912             }
14913         }
14914     }
14915     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14916         gameMode == Training || gameMode == PlayFromGameFile ||
14917         gameMode == AnalyzeFile) {
14918         while (currentMove < target) {
14919             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14920             SendMoveToProgram(currentMove++, &first);
14921         }
14922     } else {
14923         currentMove = target;
14924     }
14925
14926     if (gameMode == EditGame || gameMode == EndOfGame) {
14927         whiteTimeRemaining = timeRemaining[0][currentMove];
14928         blackTimeRemaining = timeRemaining[1][currentMove];
14929     }
14930     DisplayBothClocks();
14931     DisplayMove(currentMove - 1);
14932     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14933     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14934     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14935         DisplayComment(currentMove - 1, commentList[currentMove]);
14936     }
14937     ClearMap(); // [HGM] exclude: invalidate map
14938 }
14939
14940
14941 void
14942 ForwardEvent ()
14943 {
14944     if (gameMode == IcsExamining && !pausing) {
14945         SendToICS(ics_prefix);
14946         SendToICS("forward\n");
14947     } else {
14948         ForwardInner(currentMove + 1);
14949     }
14950 }
14951
14952 void
14953 ToEndEvent ()
14954 {
14955     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14956         /* to optimze, we temporarily turn off analysis mode while we feed
14957          * the remaining moves to the engine. Otherwise we get analysis output
14958          * after each move.
14959          */
14960         if (first.analysisSupport) {
14961           SendToProgram("exit\nforce\n", &first);
14962           first.analyzing = FALSE;
14963         }
14964     }
14965
14966     if (gameMode == IcsExamining && !pausing) {
14967         SendToICS(ics_prefix);
14968         SendToICS("forward 999999\n");
14969     } else {
14970         ForwardInner(forwardMostMove);
14971     }
14972
14973     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14974         /* we have fed all the moves, so reactivate analysis mode */
14975         SendToProgram("analyze\n", &first);
14976         first.analyzing = TRUE;
14977         /*first.maybeThinking = TRUE;*/
14978         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14979     }
14980 }
14981
14982 void
14983 BackwardInner (int target)
14984 {
14985     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14986
14987     if (appData.debugMode)
14988         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14989                 target, currentMove, forwardMostMove);
14990
14991     if (gameMode == EditPosition) return;
14992     seekGraphUp = FALSE;
14993     MarkTargetSquares(1);
14994     if (currentMove <= backwardMostMove) {
14995         ClearHighlights();
14996         DrawPosition(full_redraw, boards[currentMove]);
14997         return;
14998     }
14999     if (gameMode == PlayFromGameFile && !pausing)
15000       PauseEvent();
15001
15002     if (moveList[target][0]) {
15003         int fromX, fromY, toX, toY;
15004         toX = moveList[target][2] - AAA;
15005         toY = moveList[target][3] - ONE;
15006         if (moveList[target][1] == '@') {
15007             if (appData.highlightLastMove) {
15008                 SetHighlights(-1, -1, toX, toY);
15009             }
15010         } else {
15011             fromX = moveList[target][0] - AAA;
15012             fromY = moveList[target][1] - ONE;
15013             if (target == currentMove - 1) {
15014                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15015             }
15016             if (appData.highlightLastMove) {
15017                 SetHighlights(fromX, fromY, toX, toY);
15018             }
15019         }
15020     }
15021     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15022         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15023         while (currentMove > target) {
15024             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15025                 // null move cannot be undone. Reload program with move history before it.
15026                 int i;
15027                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15028                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15029                 }
15030                 SendBoard(&first, i);
15031               if(second.analyzing) SendBoard(&second, i);
15032                 for(currentMove=i; currentMove<target; currentMove++) {
15033                     SendMoveToProgram(currentMove, &first);
15034                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15035                 }
15036                 break;
15037             }
15038             SendToBoth("undo\n");
15039             currentMove--;
15040         }
15041     } else {
15042         currentMove = target;
15043     }
15044
15045     if (gameMode == EditGame || gameMode == EndOfGame) {
15046         whiteTimeRemaining = timeRemaining[0][currentMove];
15047         blackTimeRemaining = timeRemaining[1][currentMove];
15048     }
15049     DisplayBothClocks();
15050     DisplayMove(currentMove - 1);
15051     DrawPosition(full_redraw, boards[currentMove]);
15052     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15053     // [HGM] PV info: routine tests if comment empty
15054     DisplayComment(currentMove - 1, commentList[currentMove]);
15055     ClearMap(); // [HGM] exclude: invalidate map
15056 }
15057
15058 void
15059 BackwardEvent ()
15060 {
15061     if (gameMode == IcsExamining && !pausing) {
15062         SendToICS(ics_prefix);
15063         SendToICS("backward\n");
15064     } else {
15065         BackwardInner(currentMove - 1);
15066     }
15067 }
15068
15069 void
15070 ToStartEvent ()
15071 {
15072     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15073         /* to optimize, we temporarily turn off analysis mode while we undo
15074          * all the moves. Otherwise we get analysis output after each undo.
15075          */
15076         if (first.analysisSupport) {
15077           SendToProgram("exit\nforce\n", &first);
15078           first.analyzing = FALSE;
15079         }
15080     }
15081
15082     if (gameMode == IcsExamining && !pausing) {
15083         SendToICS(ics_prefix);
15084         SendToICS("backward 999999\n");
15085     } else {
15086         BackwardInner(backwardMostMove);
15087     }
15088
15089     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15090         /* we have fed all the moves, so reactivate analysis mode */
15091         SendToProgram("analyze\n", &first);
15092         first.analyzing = TRUE;
15093         /*first.maybeThinking = TRUE;*/
15094         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15095     }
15096 }
15097
15098 void
15099 ToNrEvent (int to)
15100 {
15101   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15102   if (to >= forwardMostMove) to = forwardMostMove;
15103   if (to <= backwardMostMove) to = backwardMostMove;
15104   if (to < currentMove) {
15105     BackwardInner(to);
15106   } else {
15107     ForwardInner(to);
15108   }
15109 }
15110
15111 void
15112 RevertEvent (Boolean annotate)
15113 {
15114     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15115         return;
15116     }
15117     if (gameMode != IcsExamining) {
15118         DisplayError(_("You are not examining a game"), 0);
15119         return;
15120     }
15121     if (pausing) {
15122         DisplayError(_("You can't revert while pausing"), 0);
15123         return;
15124     }
15125     SendToICS(ics_prefix);
15126     SendToICS("revert\n");
15127 }
15128
15129 void
15130 RetractMoveEvent ()
15131 {
15132     switch (gameMode) {
15133       case MachinePlaysWhite:
15134       case MachinePlaysBlack:
15135         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15136             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15137             return;
15138         }
15139         if (forwardMostMove < 2) return;
15140         currentMove = forwardMostMove = forwardMostMove - 2;
15141         whiteTimeRemaining = timeRemaining[0][currentMove];
15142         blackTimeRemaining = timeRemaining[1][currentMove];
15143         DisplayBothClocks();
15144         DisplayMove(currentMove - 1);
15145         ClearHighlights();/*!! could figure this out*/
15146         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15147         SendToProgram("remove\n", &first);
15148         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15149         break;
15150
15151       case BeginningOfGame:
15152       default:
15153         break;
15154
15155       case IcsPlayingWhite:
15156       case IcsPlayingBlack:
15157         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15158             SendToICS(ics_prefix);
15159             SendToICS("takeback 2\n");
15160         } else {
15161             SendToICS(ics_prefix);
15162             SendToICS("takeback 1\n");
15163         }
15164         break;
15165     }
15166 }
15167
15168 void
15169 MoveNowEvent ()
15170 {
15171     ChessProgramState *cps;
15172
15173     switch (gameMode) {
15174       case MachinePlaysWhite:
15175         if (!WhiteOnMove(forwardMostMove)) {
15176             DisplayError(_("It is your turn"), 0);
15177             return;
15178         }
15179         cps = &first;
15180         break;
15181       case MachinePlaysBlack:
15182         if (WhiteOnMove(forwardMostMove)) {
15183             DisplayError(_("It is your turn"), 0);
15184             return;
15185         }
15186         cps = &first;
15187         break;
15188       case TwoMachinesPlay:
15189         if (WhiteOnMove(forwardMostMove) ==
15190             (first.twoMachinesColor[0] == 'w')) {
15191             cps = &first;
15192         } else {
15193             cps = &second;
15194         }
15195         break;
15196       case BeginningOfGame:
15197       default:
15198         return;
15199     }
15200     SendToProgram("?\n", cps);
15201 }
15202
15203 void
15204 TruncateGameEvent ()
15205 {
15206     EditGameEvent();
15207     if (gameMode != EditGame) return;
15208     TruncateGame();
15209 }
15210
15211 void
15212 TruncateGame ()
15213 {
15214     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15215     if (forwardMostMove > currentMove) {
15216         if (gameInfo.resultDetails != NULL) {
15217             free(gameInfo.resultDetails);
15218             gameInfo.resultDetails = NULL;
15219             gameInfo.result = GameUnfinished;
15220         }
15221         forwardMostMove = currentMove;
15222         HistorySet(parseList, backwardMostMove, forwardMostMove,
15223                    currentMove-1);
15224     }
15225 }
15226
15227 void
15228 HintEvent ()
15229 {
15230     if (appData.noChessProgram) return;
15231     switch (gameMode) {
15232       case MachinePlaysWhite:
15233         if (WhiteOnMove(forwardMostMove)) {
15234             DisplayError(_("Wait until your turn"), 0);
15235             return;
15236         }
15237         break;
15238       case BeginningOfGame:
15239       case MachinePlaysBlack:
15240         if (!WhiteOnMove(forwardMostMove)) {
15241             DisplayError(_("Wait until your turn"), 0);
15242             return;
15243         }
15244         break;
15245       default:
15246         DisplayError(_("No hint available"), 0);
15247         return;
15248     }
15249     SendToProgram("hint\n", &first);
15250     hintRequested = TRUE;
15251 }
15252
15253 void
15254 CreateBookEvent ()
15255 {
15256     ListGame * lg = (ListGame *) gameList.head;
15257     FILE *f, *g;
15258     int nItem;
15259     static int secondTime = FALSE;
15260
15261     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15262         DisplayError(_("Game list not loaded or empty"), 0);
15263         return;
15264     }
15265
15266     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15267         fclose(g);
15268         secondTime++;
15269         DisplayNote(_("Book file exists! Try again for overwrite."));
15270         return;
15271     }
15272
15273     creatingBook = TRUE;
15274     secondTime = FALSE;
15275
15276     /* Get list size */
15277     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15278         LoadGame(f, nItem, "", TRUE);
15279         AddGameToBook(TRUE);
15280         lg = (ListGame *) lg->node.succ;
15281     }
15282
15283     creatingBook = FALSE;
15284     FlushBook();
15285 }
15286
15287 void
15288 BookEvent ()
15289 {
15290     if (appData.noChessProgram) return;
15291     switch (gameMode) {
15292       case MachinePlaysWhite:
15293         if (WhiteOnMove(forwardMostMove)) {
15294             DisplayError(_("Wait until your turn"), 0);
15295             return;
15296         }
15297         break;
15298       case BeginningOfGame:
15299       case MachinePlaysBlack:
15300         if (!WhiteOnMove(forwardMostMove)) {
15301             DisplayError(_("Wait until your turn"), 0);
15302             return;
15303         }
15304         break;
15305       case EditPosition:
15306         EditPositionDone(TRUE);
15307         break;
15308       case TwoMachinesPlay:
15309         return;
15310       default:
15311         break;
15312     }
15313     SendToProgram("bk\n", &first);
15314     bookOutput[0] = NULLCHAR;
15315     bookRequested = TRUE;
15316 }
15317
15318 void
15319 AboutGameEvent ()
15320 {
15321     char *tags = PGNTags(&gameInfo);
15322     TagsPopUp(tags, CmailMsg());
15323     free(tags);
15324 }
15325
15326 /* end button procedures */
15327
15328 void
15329 PrintPosition (FILE *fp, int move)
15330 {
15331     int i, j;
15332
15333     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15334         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15335             char c = PieceToChar(boards[move][i][j]);
15336             fputc(c == 'x' ? '.' : c, fp);
15337             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15338         }
15339     }
15340     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15341       fprintf(fp, "white to play\n");
15342     else
15343       fprintf(fp, "black to play\n");
15344 }
15345
15346 void
15347 PrintOpponents (FILE *fp)
15348 {
15349     if (gameInfo.white != NULL) {
15350         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15351     } else {
15352         fprintf(fp, "\n");
15353     }
15354 }
15355
15356 /* Find last component of program's own name, using some heuristics */
15357 void
15358 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15359 {
15360     char *p, *q, c;
15361     int local = (strcmp(host, "localhost") == 0);
15362     while (!local && (p = strchr(prog, ';')) != NULL) {
15363         p++;
15364         while (*p == ' ') p++;
15365         prog = p;
15366     }
15367     if (*prog == '"' || *prog == '\'') {
15368         q = strchr(prog + 1, *prog);
15369     } else {
15370         q = strchr(prog, ' ');
15371     }
15372     if (q == NULL) q = prog + strlen(prog);
15373     p = q;
15374     while (p >= prog && *p != '/' && *p != '\\') p--;
15375     p++;
15376     if(p == prog && *p == '"') p++;
15377     c = *q; *q = 0;
15378     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15379     memcpy(buf, p, q - p);
15380     buf[q - p] = NULLCHAR;
15381     if (!local) {
15382         strcat(buf, "@");
15383         strcat(buf, host);
15384     }
15385 }
15386
15387 char *
15388 TimeControlTagValue ()
15389 {
15390     char buf[MSG_SIZ];
15391     if (!appData.clockMode) {
15392       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15393     } else if (movesPerSession > 0) {
15394       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15395     } else if (timeIncrement == 0) {
15396       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15397     } else {
15398       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15399     }
15400     return StrSave(buf);
15401 }
15402
15403 void
15404 SetGameInfo ()
15405 {
15406     /* This routine is used only for certain modes */
15407     VariantClass v = gameInfo.variant;
15408     ChessMove r = GameUnfinished;
15409     char *p = NULL;
15410
15411     if(keepInfo) return;
15412
15413     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15414         r = gameInfo.result;
15415         p = gameInfo.resultDetails;
15416         gameInfo.resultDetails = NULL;
15417     }
15418     ClearGameInfo(&gameInfo);
15419     gameInfo.variant = v;
15420
15421     switch (gameMode) {
15422       case MachinePlaysWhite:
15423         gameInfo.event = StrSave( appData.pgnEventHeader );
15424         gameInfo.site = StrSave(HostName());
15425         gameInfo.date = PGNDate();
15426         gameInfo.round = StrSave("-");
15427         gameInfo.white = StrSave(first.tidy);
15428         gameInfo.black = StrSave(UserName());
15429         gameInfo.timeControl = TimeControlTagValue();
15430         break;
15431
15432       case MachinePlaysBlack:
15433         gameInfo.event = StrSave( appData.pgnEventHeader );
15434         gameInfo.site = StrSave(HostName());
15435         gameInfo.date = PGNDate();
15436         gameInfo.round = StrSave("-");
15437         gameInfo.white = StrSave(UserName());
15438         gameInfo.black = StrSave(first.tidy);
15439         gameInfo.timeControl = TimeControlTagValue();
15440         break;
15441
15442       case TwoMachinesPlay:
15443         gameInfo.event = StrSave( appData.pgnEventHeader );
15444         gameInfo.site = StrSave(HostName());
15445         gameInfo.date = PGNDate();
15446         if (roundNr > 0) {
15447             char buf[MSG_SIZ];
15448             snprintf(buf, MSG_SIZ, "%d", roundNr);
15449             gameInfo.round = StrSave(buf);
15450         } else {
15451             gameInfo.round = StrSave("-");
15452         }
15453         if (first.twoMachinesColor[0] == 'w') {
15454             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15455             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15456         } else {
15457             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15458             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15459         }
15460         gameInfo.timeControl = TimeControlTagValue();
15461         break;
15462
15463       case EditGame:
15464         gameInfo.event = StrSave("Edited game");
15465         gameInfo.site = StrSave(HostName());
15466         gameInfo.date = PGNDate();
15467         gameInfo.round = StrSave("-");
15468         gameInfo.white = StrSave("-");
15469         gameInfo.black = StrSave("-");
15470         gameInfo.result = r;
15471         gameInfo.resultDetails = p;
15472         break;
15473
15474       case EditPosition:
15475         gameInfo.event = StrSave("Edited position");
15476         gameInfo.site = StrSave(HostName());
15477         gameInfo.date = PGNDate();
15478         gameInfo.round = StrSave("-");
15479         gameInfo.white = StrSave("-");
15480         gameInfo.black = StrSave("-");
15481         break;
15482
15483       case IcsPlayingWhite:
15484       case IcsPlayingBlack:
15485       case IcsObserving:
15486       case IcsExamining:
15487         break;
15488
15489       case PlayFromGameFile:
15490         gameInfo.event = StrSave("Game from non-PGN file");
15491         gameInfo.site = StrSave(HostName());
15492         gameInfo.date = PGNDate();
15493         gameInfo.round = StrSave("-");
15494         gameInfo.white = StrSave("?");
15495         gameInfo.black = StrSave("?");
15496         break;
15497
15498       default:
15499         break;
15500     }
15501 }
15502
15503 void
15504 ReplaceComment (int index, char *text)
15505 {
15506     int len;
15507     char *p;
15508     float score;
15509
15510     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15511        pvInfoList[index-1].depth == len &&
15512        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15513        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15514     while (*text == '\n') text++;
15515     len = strlen(text);
15516     while (len > 0 && text[len - 1] == '\n') len--;
15517
15518     if (commentList[index] != NULL)
15519       free(commentList[index]);
15520
15521     if (len == 0) {
15522         commentList[index] = NULL;
15523         return;
15524     }
15525   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15526       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15527       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15528     commentList[index] = (char *) malloc(len + 2);
15529     strncpy(commentList[index], text, len);
15530     commentList[index][len] = '\n';
15531     commentList[index][len + 1] = NULLCHAR;
15532   } else {
15533     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15534     char *p;
15535     commentList[index] = (char *) malloc(len + 7);
15536     safeStrCpy(commentList[index], "{\n", 3);
15537     safeStrCpy(commentList[index]+2, text, len+1);
15538     commentList[index][len+2] = NULLCHAR;
15539     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15540     strcat(commentList[index], "\n}\n");
15541   }
15542 }
15543
15544 void
15545 CrushCRs (char *text)
15546 {
15547   char *p = text;
15548   char *q = text;
15549   char ch;
15550
15551   do {
15552     ch = *p++;
15553     if (ch == '\r') continue;
15554     *q++ = ch;
15555   } while (ch != '\0');
15556 }
15557
15558 void
15559 AppendComment (int index, char *text, Boolean addBraces)
15560 /* addBraces  tells if we should add {} */
15561 {
15562     int oldlen, len;
15563     char *old;
15564
15565 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15566     if(addBraces == 3) addBraces = 0; else // force appending literally
15567     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15568
15569     CrushCRs(text);
15570     while (*text == '\n') text++;
15571     len = strlen(text);
15572     while (len > 0 && text[len - 1] == '\n') len--;
15573     text[len] = NULLCHAR;
15574
15575     if (len == 0) return;
15576
15577     if (commentList[index] != NULL) {
15578       Boolean addClosingBrace = addBraces;
15579         old = commentList[index];
15580         oldlen = strlen(old);
15581         while(commentList[index][oldlen-1] ==  '\n')
15582           commentList[index][--oldlen] = NULLCHAR;
15583         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15584         safeStrCpy(commentList[index], old, oldlen + len + 6);
15585         free(old);
15586         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15587         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15588           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15589           while (*text == '\n') { text++; len--; }
15590           commentList[index][--oldlen] = NULLCHAR;
15591       }
15592         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15593         else          strcat(commentList[index], "\n");
15594         strcat(commentList[index], text);
15595         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15596         else          strcat(commentList[index], "\n");
15597     } else {
15598         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15599         if(addBraces)
15600           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15601         else commentList[index][0] = NULLCHAR;
15602         strcat(commentList[index], text);
15603         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15604         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15605     }
15606 }
15607
15608 static char *
15609 FindStr (char * text, char * sub_text)
15610 {
15611     char * result = strstr( text, sub_text );
15612
15613     if( result != NULL ) {
15614         result += strlen( sub_text );
15615     }
15616
15617     return result;
15618 }
15619
15620 /* [AS] Try to extract PV info from PGN comment */
15621 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15622 char *
15623 GetInfoFromComment (int index, char * text)
15624 {
15625     char * sep = text, *p;
15626
15627     if( text != NULL && index > 0 ) {
15628         int score = 0;
15629         int depth = 0;
15630         int time = -1, sec = 0, deci;
15631         char * s_eval = FindStr( text, "[%eval " );
15632         char * s_emt = FindStr( text, "[%emt " );
15633 #if 0
15634         if( s_eval != NULL || s_emt != NULL ) {
15635 #else
15636         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15637 #endif
15638             /* New style */
15639             char delim;
15640
15641             if( s_eval != NULL ) {
15642                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15643                     return text;
15644                 }
15645
15646                 if( delim != ']' ) {
15647                     return text;
15648                 }
15649             }
15650
15651             if( s_emt != NULL ) {
15652             }
15653                 return text;
15654         }
15655         else {
15656             /* We expect something like: [+|-]nnn.nn/dd */
15657             int score_lo = 0;
15658
15659             if(*text != '{') return text; // [HGM] braces: must be normal comment
15660
15661             sep = strchr( text, '/' );
15662             if( sep == NULL || sep < (text+4) ) {
15663                 return text;
15664             }
15665
15666             p = text;
15667             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15668             if(p[1] == '(') { // comment starts with PV
15669                p = strchr(p, ')'); // locate end of PV
15670                if(p == NULL || sep < p+5) return text;
15671                // at this point we have something like "{(.*) +0.23/6 ..."
15672                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15673                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15674                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15675             }
15676             time = -1; sec = -1; deci = -1;
15677             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15678                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15679                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15680                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15681                 return text;
15682             }
15683
15684             if( score_lo < 0 || score_lo >= 100 ) {
15685                 return text;
15686             }
15687
15688             if(sec >= 0) time = 600*time + 10*sec; else
15689             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15690
15691             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15692
15693             /* [HGM] PV time: now locate end of PV info */
15694             while( *++sep >= '0' && *sep <= '9'); // strip depth
15695             if(time >= 0)
15696             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15697             if(sec >= 0)
15698             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15699             if(deci >= 0)
15700             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15701             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15702         }
15703
15704         if( depth <= 0 ) {
15705             return text;
15706         }
15707
15708         if( time < 0 ) {
15709             time = -1;
15710         }
15711
15712         pvInfoList[index-1].depth = depth;
15713         pvInfoList[index-1].score = score;
15714         pvInfoList[index-1].time  = 10*time; // centi-sec
15715         if(*sep == '}') *sep = 0; else *--sep = '{';
15716         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15717     }
15718     return sep;
15719 }
15720
15721 void
15722 SendToProgram (char *message, ChessProgramState *cps)
15723 {
15724     int count, outCount, error;
15725     char buf[MSG_SIZ];
15726
15727     if (cps->pr == NoProc) return;
15728     Attention(cps);
15729
15730     if (appData.debugMode) {
15731         TimeMark now;
15732         GetTimeMark(&now);
15733         fprintf(debugFP, "%ld >%-6s: %s",
15734                 SubtractTimeMarks(&now, &programStartTime),
15735                 cps->which, message);
15736         if(serverFP)
15737             fprintf(serverFP, "%ld >%-6s: %s",
15738                 SubtractTimeMarks(&now, &programStartTime),
15739                 cps->which, message), fflush(serverFP);
15740     }
15741
15742     count = strlen(message);
15743     outCount = OutputToProcess(cps->pr, message, count, &error);
15744     if (outCount < count && !exiting
15745                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15746       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15747       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15748         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15749             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15750                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15751                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15752                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15753             } else {
15754                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15755                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15756                 gameInfo.result = res;
15757             }
15758             gameInfo.resultDetails = StrSave(buf);
15759         }
15760         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15761         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15762     }
15763 }
15764
15765 void
15766 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15767 {
15768     char *end_str;
15769     char buf[MSG_SIZ];
15770     ChessProgramState *cps = (ChessProgramState *)closure;
15771
15772     if (isr != cps->isr) return; /* Killed intentionally */
15773     if (count <= 0) {
15774         if (count == 0) {
15775             RemoveInputSource(cps->isr);
15776             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15777                     _(cps->which), cps->program);
15778             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15779             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15780                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15781                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15782                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15783                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15784                 } else {
15785                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15786                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15787                     gameInfo.result = res;
15788                 }
15789                 gameInfo.resultDetails = StrSave(buf);
15790             }
15791             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15792             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15793         } else {
15794             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15795                     _(cps->which), cps->program);
15796             RemoveInputSource(cps->isr);
15797
15798             /* [AS] Program is misbehaving badly... kill it */
15799             if( count == -2 ) {
15800                 DestroyChildProcess( cps->pr, 9 );
15801                 cps->pr = NoProc;
15802             }
15803
15804             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15805         }
15806         return;
15807     }
15808
15809     if ((end_str = strchr(message, '\r')) != NULL)
15810       *end_str = NULLCHAR;
15811     if ((end_str = strchr(message, '\n')) != NULL)
15812       *end_str = NULLCHAR;
15813
15814     if (appData.debugMode) {
15815         TimeMark now; int print = 1;
15816         char *quote = ""; char c; int i;
15817
15818         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15819                 char start = message[0];
15820                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15821                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15822                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15823                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15824                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15825                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15826                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15827                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15828                    sscanf(message, "hint: %c", &c)!=1 &&
15829                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15830                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15831                     print = (appData.engineComments >= 2);
15832                 }
15833                 message[0] = start; // restore original message
15834         }
15835         if(print) {
15836                 GetTimeMark(&now);
15837                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15838                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15839                         quote,
15840                         message);
15841                 if(serverFP)
15842                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15843                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15844                         quote,
15845                         message), fflush(serverFP);
15846         }
15847     }
15848
15849     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15850     if (appData.icsEngineAnalyze) {
15851         if (strstr(message, "whisper") != NULL ||
15852              strstr(message, "kibitz") != NULL ||
15853             strstr(message, "tellics") != NULL) return;
15854     }
15855
15856     HandleMachineMove(message, cps);
15857 }
15858
15859
15860 void
15861 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15862 {
15863     char buf[MSG_SIZ];
15864     int seconds;
15865
15866     if( timeControl_2 > 0 ) {
15867         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15868             tc = timeControl_2;
15869         }
15870     }
15871     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15872     inc /= cps->timeOdds;
15873     st  /= cps->timeOdds;
15874
15875     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15876
15877     if (st > 0) {
15878       /* Set exact time per move, normally using st command */
15879       if (cps->stKludge) {
15880         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15881         seconds = st % 60;
15882         if (seconds == 0) {
15883           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15884         } else {
15885           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15886         }
15887       } else {
15888         snprintf(buf, MSG_SIZ, "st %d\n", st);
15889       }
15890     } else {
15891       /* Set conventional or incremental time control, using level command */
15892       if (seconds == 0) {
15893         /* Note old gnuchess bug -- minutes:seconds used to not work.
15894            Fixed in later versions, but still avoid :seconds
15895            when seconds is 0. */
15896         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15897       } else {
15898         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15899                  seconds, inc/1000.);
15900       }
15901     }
15902     SendToProgram(buf, cps);
15903
15904     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15905     /* Orthogonally, limit search to given depth */
15906     if (sd > 0) {
15907       if (cps->sdKludge) {
15908         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15909       } else {
15910         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15911       }
15912       SendToProgram(buf, cps);
15913     }
15914
15915     if(cps->nps >= 0) { /* [HGM] nps */
15916         if(cps->supportsNPS == FALSE)
15917           cps->nps = -1; // don't use if engine explicitly says not supported!
15918         else {
15919           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15920           SendToProgram(buf, cps);
15921         }
15922     }
15923 }
15924
15925 ChessProgramState *
15926 WhitePlayer ()
15927 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15928 {
15929     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15930        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15931         return &second;
15932     return &first;
15933 }
15934
15935 void
15936 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15937 {
15938     char message[MSG_SIZ];
15939     long time, otime;
15940
15941     /* Note: this routine must be called when the clocks are stopped
15942        or when they have *just* been set or switched; otherwise
15943        it will be off by the time since the current tick started.
15944     */
15945     if (machineWhite) {
15946         time = whiteTimeRemaining / 10;
15947         otime = blackTimeRemaining / 10;
15948     } else {
15949         time = blackTimeRemaining / 10;
15950         otime = whiteTimeRemaining / 10;
15951     }
15952     /* [HGM] translate opponent's time by time-odds factor */
15953     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15954
15955     if (time <= 0) time = 1;
15956     if (otime <= 0) otime = 1;
15957
15958     snprintf(message, MSG_SIZ, "time %ld\n", time);
15959     SendToProgram(message, cps);
15960
15961     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15962     SendToProgram(message, cps);
15963 }
15964
15965 int
15966 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15967 {
15968   char buf[MSG_SIZ];
15969   int len = strlen(name);
15970   int val;
15971
15972   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15973     (*p) += len + 1;
15974     sscanf(*p, "%d", &val);
15975     *loc = (val != 0);
15976     while (**p && **p != ' ')
15977       (*p)++;
15978     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15979     SendToProgram(buf, cps);
15980     return TRUE;
15981   }
15982   return FALSE;
15983 }
15984
15985 int
15986 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15987 {
15988   char buf[MSG_SIZ];
15989   int len = strlen(name);
15990   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15991     (*p) += len + 1;
15992     sscanf(*p, "%d", loc);
15993     while (**p && **p != ' ') (*p)++;
15994     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15995     SendToProgram(buf, cps);
15996     return TRUE;
15997   }
15998   return FALSE;
15999 }
16000
16001 int
16002 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16003 {
16004   char buf[MSG_SIZ];
16005   int len = strlen(name);
16006   if (strncmp((*p), name, len) == 0
16007       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16008     (*p) += len + 2;
16009     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16010     sscanf(*p, "%[^\"]", *loc);
16011     while (**p && **p != '\"') (*p)++;
16012     if (**p == '\"') (*p)++;
16013     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16014     SendToProgram(buf, cps);
16015     return TRUE;
16016   }
16017   return FALSE;
16018 }
16019
16020 int
16021 ParseOption (Option *opt, ChessProgramState *cps)
16022 // [HGM] options: process the string that defines an engine option, and determine
16023 // name, type, default value, and allowed value range
16024 {
16025         char *p, *q, buf[MSG_SIZ];
16026         int n, min = (-1)<<31, max = 1<<31, def;
16027
16028         if(p = strstr(opt->name, " -spin ")) {
16029             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16030             if(max < min) max = min; // enforce consistency
16031             if(def < min) def = min;
16032             if(def > max) def = max;
16033             opt->value = def;
16034             opt->min = min;
16035             opt->max = max;
16036             opt->type = Spin;
16037         } else if((p = strstr(opt->name, " -slider "))) {
16038             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16039             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16040             if(max < min) max = min; // enforce consistency
16041             if(def < min) def = min;
16042             if(def > max) def = max;
16043             opt->value = def;
16044             opt->min = min;
16045             opt->max = max;
16046             opt->type = Spin; // Slider;
16047         } else if((p = strstr(opt->name, " -string "))) {
16048             opt->textValue = p+9;
16049             opt->type = TextBox;
16050         } else if((p = strstr(opt->name, " -file "))) {
16051             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16052             opt->textValue = p+7;
16053             opt->type = FileName; // FileName;
16054         } else if((p = strstr(opt->name, " -path "))) {
16055             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16056             opt->textValue = p+7;
16057             opt->type = PathName; // PathName;
16058         } else if(p = strstr(opt->name, " -check ")) {
16059             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16060             opt->value = (def != 0);
16061             opt->type = CheckBox;
16062         } else if(p = strstr(opt->name, " -combo ")) {
16063             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16064             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16065             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16066             opt->value = n = 0;
16067             while(q = StrStr(q, " /// ")) {
16068                 n++; *q = 0;    // count choices, and null-terminate each of them
16069                 q += 5;
16070                 if(*q == '*') { // remember default, which is marked with * prefix
16071                     q++;
16072                     opt->value = n;
16073                 }
16074                 cps->comboList[cps->comboCnt++] = q;
16075             }
16076             cps->comboList[cps->comboCnt++] = NULL;
16077             opt->max = n + 1;
16078             opt->type = ComboBox;
16079         } else if(p = strstr(opt->name, " -button")) {
16080             opt->type = Button;
16081         } else if(p = strstr(opt->name, " -save")) {
16082             opt->type = SaveButton;
16083         } else return FALSE;
16084         *p = 0; // terminate option name
16085         // now look if the command-line options define a setting for this engine option.
16086         if(cps->optionSettings && cps->optionSettings[0])
16087             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16088         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16089           snprintf(buf, MSG_SIZ, "option %s", p);
16090                 if(p = strstr(buf, ",")) *p = 0;
16091                 if(q = strchr(buf, '=')) switch(opt->type) {
16092                     case ComboBox:
16093                         for(n=0; n<opt->max; n++)
16094                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16095                         break;
16096                     case TextBox:
16097                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16098                         break;
16099                     case Spin:
16100                     case CheckBox:
16101                         opt->value = atoi(q+1);
16102                     default:
16103                         break;
16104                 }
16105                 strcat(buf, "\n");
16106                 SendToProgram(buf, cps);
16107         }
16108         return TRUE;
16109 }
16110
16111 void
16112 FeatureDone (ChessProgramState *cps, int val)
16113 {
16114   DelayedEventCallback cb = GetDelayedEvent();
16115   if ((cb == InitBackEnd3 && cps == &first) ||
16116       (cb == SettingsMenuIfReady && cps == &second) ||
16117       (cb == LoadEngine) ||
16118       (cb == TwoMachinesEventIfReady)) {
16119     CancelDelayedEvent();
16120     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16121   }
16122   cps->initDone = val;
16123   if(val) cps->reload = FALSE;
16124 }
16125
16126 /* Parse feature command from engine */
16127 void
16128 ParseFeatures (char *args, ChessProgramState *cps)
16129 {
16130   char *p = args;
16131   char *q = NULL;
16132   int val;
16133   char buf[MSG_SIZ];
16134
16135   for (;;) {
16136     while (*p == ' ') p++;
16137     if (*p == NULLCHAR) return;
16138
16139     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16140     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16141     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16142     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16143     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16144     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16145     if (BoolFeature(&p, "reuse", &val, cps)) {
16146       /* Engine can disable reuse, but can't enable it if user said no */
16147       if (!val) cps->reuse = FALSE;
16148       continue;
16149     }
16150     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16151     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16152       if (gameMode == TwoMachinesPlay) {
16153         DisplayTwoMachinesTitle();
16154       } else {
16155         DisplayTitle("");
16156       }
16157       continue;
16158     }
16159     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16160     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16161     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16162     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16163     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16164     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16165     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16166     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16167     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16168     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16169     if (IntFeature(&p, "done", &val, cps)) {
16170       FeatureDone(cps, val);
16171       continue;
16172     }
16173     /* Added by Tord: */
16174     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16175     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16176     /* End of additions by Tord */
16177
16178     /* [HGM] added features: */
16179     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16180     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16181     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16182     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16183     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16184     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16185     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16186         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16187         FREE(cps->option[cps->nrOptions].name);
16188         cps->option[cps->nrOptions].name = q; q = NULL;
16189         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16190           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16191             SendToProgram(buf, cps);
16192             continue;
16193         }
16194         if(cps->nrOptions >= MAX_OPTIONS) {
16195             cps->nrOptions--;
16196             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16197             DisplayError(buf, 0);
16198         }
16199         continue;
16200     }
16201     /* End of additions by HGM */
16202
16203     /* unknown feature: complain and skip */
16204     q = p;
16205     while (*q && *q != '=') q++;
16206     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16207     SendToProgram(buf, cps);
16208     p = q;
16209     if (*p == '=') {
16210       p++;
16211       if (*p == '\"') {
16212         p++;
16213         while (*p && *p != '\"') p++;
16214         if (*p == '\"') p++;
16215       } else {
16216         while (*p && *p != ' ') p++;
16217       }
16218     }
16219   }
16220
16221 }
16222
16223 void
16224 PeriodicUpdatesEvent (int newState)
16225 {
16226     if (newState == appData.periodicUpdates)
16227       return;
16228
16229     appData.periodicUpdates=newState;
16230
16231     /* Display type changes, so update it now */
16232 //    DisplayAnalysis();
16233
16234     /* Get the ball rolling again... */
16235     if (newState) {
16236         AnalysisPeriodicEvent(1);
16237         StartAnalysisClock();
16238     }
16239 }
16240
16241 void
16242 PonderNextMoveEvent (int newState)
16243 {
16244     if (newState == appData.ponderNextMove) return;
16245     if (gameMode == EditPosition) EditPositionDone(TRUE);
16246     if (newState) {
16247         SendToProgram("hard\n", &first);
16248         if (gameMode == TwoMachinesPlay) {
16249             SendToProgram("hard\n", &second);
16250         }
16251     } else {
16252         SendToProgram("easy\n", &first);
16253         thinkOutput[0] = NULLCHAR;
16254         if (gameMode == TwoMachinesPlay) {
16255             SendToProgram("easy\n", &second);
16256         }
16257     }
16258     appData.ponderNextMove = newState;
16259 }
16260
16261 void
16262 NewSettingEvent (int option, int *feature, char *command, int value)
16263 {
16264     char buf[MSG_SIZ];
16265
16266     if (gameMode == EditPosition) EditPositionDone(TRUE);
16267     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16268     if(feature == NULL || *feature) SendToProgram(buf, &first);
16269     if (gameMode == TwoMachinesPlay) {
16270         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16271     }
16272 }
16273
16274 void
16275 ShowThinkingEvent ()
16276 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16277 {
16278     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16279     int newState = appData.showThinking
16280         // [HGM] thinking: other features now need thinking output as well
16281         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16282
16283     if (oldState == newState) return;
16284     oldState = newState;
16285     if (gameMode == EditPosition) EditPositionDone(TRUE);
16286     if (oldState) {
16287         SendToProgram("post\n", &first);
16288         if (gameMode == TwoMachinesPlay) {
16289             SendToProgram("post\n", &second);
16290         }
16291     } else {
16292         SendToProgram("nopost\n", &first);
16293         thinkOutput[0] = NULLCHAR;
16294         if (gameMode == TwoMachinesPlay) {
16295             SendToProgram("nopost\n", &second);
16296         }
16297     }
16298 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16299 }
16300
16301 void
16302 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16303 {
16304   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16305   if (pr == NoProc) return;
16306   AskQuestion(title, question, replyPrefix, pr);
16307 }
16308
16309 void
16310 TypeInEvent (char firstChar)
16311 {
16312     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16313         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16314         gameMode == AnalyzeMode || gameMode == EditGame ||
16315         gameMode == EditPosition || gameMode == IcsExamining ||
16316         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16317         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16318                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16319                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16320         gameMode == Training) PopUpMoveDialog(firstChar);
16321 }
16322
16323 void
16324 TypeInDoneEvent (char *move)
16325 {
16326         Board board;
16327         int n, fromX, fromY, toX, toY;
16328         char promoChar;
16329         ChessMove moveType;
16330
16331         // [HGM] FENedit
16332         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16333                 EditPositionPasteFEN(move);
16334                 return;
16335         }
16336         // [HGM] movenum: allow move number to be typed in any mode
16337         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16338           ToNrEvent(2*n-1);
16339           return;
16340         }
16341         // undocumented kludge: allow command-line option to be typed in!
16342         // (potentially fatal, and does not implement the effect of the option.)
16343         // should only be used for options that are values on which future decisions will be made,
16344         // and definitely not on options that would be used during initialization.
16345         if(strstr(move, "!!! -") == move) {
16346             ParseArgsFromString(move+4);
16347             return;
16348         }
16349
16350       if (gameMode != EditGame && currentMove != forwardMostMove &&
16351         gameMode != Training) {
16352         DisplayMoveError(_("Displayed move is not current"));
16353       } else {
16354         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16355           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16356         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16357         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16358           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16359           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16360         } else {
16361           DisplayMoveError(_("Could not parse move"));
16362         }
16363       }
16364 }
16365
16366 void
16367 DisplayMove (int moveNumber)
16368 {
16369     char message[MSG_SIZ];
16370     char res[MSG_SIZ];
16371     char cpThinkOutput[MSG_SIZ];
16372
16373     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16374
16375     if (moveNumber == forwardMostMove - 1 ||
16376         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16377
16378         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16379
16380         if (strchr(cpThinkOutput, '\n')) {
16381             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16382         }
16383     } else {
16384         *cpThinkOutput = NULLCHAR;
16385     }
16386
16387     /* [AS] Hide thinking from human user */
16388     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16389         *cpThinkOutput = NULLCHAR;
16390         if( thinkOutput[0] != NULLCHAR ) {
16391             int i;
16392
16393             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16394                 cpThinkOutput[i] = '.';
16395             }
16396             cpThinkOutput[i] = NULLCHAR;
16397             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16398         }
16399     }
16400
16401     if (moveNumber == forwardMostMove - 1 &&
16402         gameInfo.resultDetails != NULL) {
16403         if (gameInfo.resultDetails[0] == NULLCHAR) {
16404           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16405         } else {
16406           snprintf(res, MSG_SIZ, " {%s} %s",
16407                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16408         }
16409     } else {
16410         res[0] = NULLCHAR;
16411     }
16412
16413     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16414         DisplayMessage(res, cpThinkOutput);
16415     } else {
16416       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16417                 WhiteOnMove(moveNumber) ? " " : ".. ",
16418                 parseList[moveNumber], res);
16419         DisplayMessage(message, cpThinkOutput);
16420     }
16421 }
16422
16423 void
16424 DisplayComment (int moveNumber, char *text)
16425 {
16426     char title[MSG_SIZ];
16427
16428     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16429       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16430     } else {
16431       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16432               WhiteOnMove(moveNumber) ? " " : ".. ",
16433               parseList[moveNumber]);
16434     }
16435     if (text != NULL && (appData.autoDisplayComment || commentUp))
16436         CommentPopUp(title, text);
16437 }
16438
16439 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16440  * might be busy thinking or pondering.  It can be omitted if your
16441  * gnuchess is configured to stop thinking immediately on any user
16442  * input.  However, that gnuchess feature depends on the FIONREAD
16443  * ioctl, which does not work properly on some flavors of Unix.
16444  */
16445 void
16446 Attention (ChessProgramState *cps)
16447 {
16448 #if ATTENTION
16449     if (!cps->useSigint) return;
16450     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16451     switch (gameMode) {
16452       case MachinePlaysWhite:
16453       case MachinePlaysBlack:
16454       case TwoMachinesPlay:
16455       case IcsPlayingWhite:
16456       case IcsPlayingBlack:
16457       case AnalyzeMode:
16458       case AnalyzeFile:
16459         /* Skip if we know it isn't thinking */
16460         if (!cps->maybeThinking) return;
16461         if (appData.debugMode)
16462           fprintf(debugFP, "Interrupting %s\n", cps->which);
16463         InterruptChildProcess(cps->pr);
16464         cps->maybeThinking = FALSE;
16465         break;
16466       default:
16467         break;
16468     }
16469 #endif /*ATTENTION*/
16470 }
16471
16472 int
16473 CheckFlags ()
16474 {
16475     if (whiteTimeRemaining <= 0) {
16476         if (!whiteFlag) {
16477             whiteFlag = TRUE;
16478             if (appData.icsActive) {
16479                 if (appData.autoCallFlag &&
16480                     gameMode == IcsPlayingBlack && !blackFlag) {
16481                   SendToICS(ics_prefix);
16482                   SendToICS("flag\n");
16483                 }
16484             } else {
16485                 if (blackFlag) {
16486                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16487                 } else {
16488                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16489                     if (appData.autoCallFlag) {
16490                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16491                         return TRUE;
16492                     }
16493                 }
16494             }
16495         }
16496     }
16497     if (blackTimeRemaining <= 0) {
16498         if (!blackFlag) {
16499             blackFlag = TRUE;
16500             if (appData.icsActive) {
16501                 if (appData.autoCallFlag &&
16502                     gameMode == IcsPlayingWhite && !whiteFlag) {
16503                   SendToICS(ics_prefix);
16504                   SendToICS("flag\n");
16505                 }
16506             } else {
16507                 if (whiteFlag) {
16508                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16509                 } else {
16510                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16511                     if (appData.autoCallFlag) {
16512                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16513                         return TRUE;
16514                     }
16515                 }
16516             }
16517         }
16518     }
16519     return FALSE;
16520 }
16521
16522 void
16523 CheckTimeControl ()
16524 {
16525     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16526         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16527
16528     /*
16529      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16530      */
16531     if ( !WhiteOnMove(forwardMostMove) ) {
16532         /* White made time control */
16533         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16534         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16535         /* [HGM] time odds: correct new time quota for time odds! */
16536                                             / WhitePlayer()->timeOdds;
16537         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16538     } else {
16539         lastBlack -= blackTimeRemaining;
16540         /* Black made time control */
16541         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16542                                             / WhitePlayer()->other->timeOdds;
16543         lastWhite = whiteTimeRemaining;
16544     }
16545 }
16546
16547 void
16548 DisplayBothClocks ()
16549 {
16550     int wom = gameMode == EditPosition ?
16551       !blackPlaysFirst : WhiteOnMove(currentMove);
16552     DisplayWhiteClock(whiteTimeRemaining, wom);
16553     DisplayBlackClock(blackTimeRemaining, !wom);
16554 }
16555
16556
16557 /* Timekeeping seems to be a portability nightmare.  I think everyone
16558    has ftime(), but I'm really not sure, so I'm including some ifdefs
16559    to use other calls if you don't.  Clocks will be less accurate if
16560    you have neither ftime nor gettimeofday.
16561 */
16562
16563 /* VS 2008 requires the #include outside of the function */
16564 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16565 #include <sys/timeb.h>
16566 #endif
16567
16568 /* Get the current time as a TimeMark */
16569 void
16570 GetTimeMark (TimeMark *tm)
16571 {
16572 #if HAVE_GETTIMEOFDAY
16573
16574     struct timeval timeVal;
16575     struct timezone timeZone;
16576
16577     gettimeofday(&timeVal, &timeZone);
16578     tm->sec = (long) timeVal.tv_sec;
16579     tm->ms = (int) (timeVal.tv_usec / 1000L);
16580
16581 #else /*!HAVE_GETTIMEOFDAY*/
16582 #if HAVE_FTIME
16583
16584 // include <sys/timeb.h> / moved to just above start of function
16585     struct timeb timeB;
16586
16587     ftime(&timeB);
16588     tm->sec = (long) timeB.time;
16589     tm->ms = (int) timeB.millitm;
16590
16591 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16592     tm->sec = (long) time(NULL);
16593     tm->ms = 0;
16594 #endif
16595 #endif
16596 }
16597
16598 /* Return the difference in milliseconds between two
16599    time marks.  We assume the difference will fit in a long!
16600 */
16601 long
16602 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16603 {
16604     return 1000L*(tm2->sec - tm1->sec) +
16605            (long) (tm2->ms - tm1->ms);
16606 }
16607
16608
16609 /*
16610  * Code to manage the game clocks.
16611  *
16612  * In tournament play, black starts the clock and then white makes a move.
16613  * We give the human user a slight advantage if he is playing white---the
16614  * clocks don't run until he makes his first move, so it takes zero time.
16615  * Also, we don't account for network lag, so we could get out of sync
16616  * with GNU Chess's clock -- but then, referees are always right.
16617  */
16618
16619 static TimeMark tickStartTM;
16620 static long intendedTickLength;
16621
16622 long
16623 NextTickLength (long timeRemaining)
16624 {
16625     long nominalTickLength, nextTickLength;
16626
16627     if (timeRemaining > 0L && timeRemaining <= 10000L)
16628       nominalTickLength = 100L;
16629     else
16630       nominalTickLength = 1000L;
16631     nextTickLength = timeRemaining % nominalTickLength;
16632     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16633
16634     return nextTickLength;
16635 }
16636
16637 /* Adjust clock one minute up or down */
16638 void
16639 AdjustClock (Boolean which, int dir)
16640 {
16641     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16642     if(which) blackTimeRemaining += 60000*dir;
16643     else      whiteTimeRemaining += 60000*dir;
16644     DisplayBothClocks();
16645     adjustedClock = TRUE;
16646 }
16647
16648 /* Stop clocks and reset to a fresh time control */
16649 void
16650 ResetClocks ()
16651 {
16652     (void) StopClockTimer();
16653     if (appData.icsActive) {
16654         whiteTimeRemaining = blackTimeRemaining = 0;
16655     } else if (searchTime) {
16656         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16657         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16658     } else { /* [HGM] correct new time quote for time odds */
16659         whiteTC = blackTC = fullTimeControlString;
16660         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16661         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16662     }
16663     if (whiteFlag || blackFlag) {
16664         DisplayTitle("");
16665         whiteFlag = blackFlag = FALSE;
16666     }
16667     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16668     DisplayBothClocks();
16669     adjustedClock = FALSE;
16670 }
16671
16672 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16673
16674 /* Decrement running clock by amount of time that has passed */
16675 void
16676 DecrementClocks ()
16677 {
16678     long timeRemaining;
16679     long lastTickLength, fudge;
16680     TimeMark now;
16681
16682     if (!appData.clockMode) return;
16683     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16684
16685     GetTimeMark(&now);
16686
16687     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16688
16689     /* Fudge if we woke up a little too soon */
16690     fudge = intendedTickLength - lastTickLength;
16691     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16692
16693     if (WhiteOnMove(forwardMostMove)) {
16694         if(whiteNPS >= 0) lastTickLength = 0;
16695         timeRemaining = whiteTimeRemaining -= lastTickLength;
16696         if(timeRemaining < 0 && !appData.icsActive) {
16697             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16698             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16699                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16700                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16701             }
16702         }
16703         DisplayWhiteClock(whiteTimeRemaining - fudge,
16704                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16705     } else {
16706         if(blackNPS >= 0) lastTickLength = 0;
16707         timeRemaining = blackTimeRemaining -= lastTickLength;
16708         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16709             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16710             if(suddenDeath) {
16711                 blackStartMove = forwardMostMove;
16712                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16713             }
16714         }
16715         DisplayBlackClock(blackTimeRemaining - fudge,
16716                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16717     }
16718     if (CheckFlags()) return;
16719
16720     if(twoBoards) { // count down secondary board's clocks as well
16721         activePartnerTime -= lastTickLength;
16722         partnerUp = 1;
16723         if(activePartner == 'W')
16724             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16725         else
16726             DisplayBlackClock(activePartnerTime, TRUE);
16727         partnerUp = 0;
16728     }
16729
16730     tickStartTM = now;
16731     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16732     StartClockTimer(intendedTickLength);
16733
16734     /* if the time remaining has fallen below the alarm threshold, sound the
16735      * alarm. if the alarm has sounded and (due to a takeback or time control
16736      * with increment) the time remaining has increased to a level above the
16737      * threshold, reset the alarm so it can sound again.
16738      */
16739
16740     if (appData.icsActive && appData.icsAlarm) {
16741
16742         /* make sure we are dealing with the user's clock */
16743         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16744                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16745            )) return;
16746
16747         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16748             alarmSounded = FALSE;
16749         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16750             PlayAlarmSound();
16751             alarmSounded = TRUE;
16752         }
16753     }
16754 }
16755
16756
16757 /* A player has just moved, so stop the previously running
16758    clock and (if in clock mode) start the other one.
16759    We redisplay both clocks in case we're in ICS mode, because
16760    ICS gives us an update to both clocks after every move.
16761    Note that this routine is called *after* forwardMostMove
16762    is updated, so the last fractional tick must be subtracted
16763    from the color that is *not* on move now.
16764 */
16765 void
16766 SwitchClocks (int newMoveNr)
16767 {
16768     long lastTickLength;
16769     TimeMark now;
16770     int flagged = FALSE;
16771
16772     GetTimeMark(&now);
16773
16774     if (StopClockTimer() && appData.clockMode) {
16775         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16776         if (!WhiteOnMove(forwardMostMove)) {
16777             if(blackNPS >= 0) lastTickLength = 0;
16778             blackTimeRemaining -= lastTickLength;
16779            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16780 //         if(pvInfoList[forwardMostMove].time == -1)
16781                  pvInfoList[forwardMostMove].time =               // use GUI time
16782                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16783         } else {
16784            if(whiteNPS >= 0) lastTickLength = 0;
16785            whiteTimeRemaining -= lastTickLength;
16786            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16787 //         if(pvInfoList[forwardMostMove].time == -1)
16788                  pvInfoList[forwardMostMove].time =
16789                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16790         }
16791         flagged = CheckFlags();
16792     }
16793     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16794     CheckTimeControl();
16795
16796     if (flagged || !appData.clockMode) return;
16797
16798     switch (gameMode) {
16799       case MachinePlaysBlack:
16800       case MachinePlaysWhite:
16801       case BeginningOfGame:
16802         if (pausing) return;
16803         break;
16804
16805       case EditGame:
16806       case PlayFromGameFile:
16807       case IcsExamining:
16808         return;
16809
16810       default:
16811         break;
16812     }
16813
16814     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16815         if(WhiteOnMove(forwardMostMove))
16816              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16817         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16818     }
16819
16820     tickStartTM = now;
16821     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16822       whiteTimeRemaining : blackTimeRemaining);
16823     StartClockTimer(intendedTickLength);
16824 }
16825
16826
16827 /* Stop both clocks */
16828 void
16829 StopClocks ()
16830 {
16831     long lastTickLength;
16832     TimeMark now;
16833
16834     if (!StopClockTimer()) return;
16835     if (!appData.clockMode) return;
16836
16837     GetTimeMark(&now);
16838
16839     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16840     if (WhiteOnMove(forwardMostMove)) {
16841         if(whiteNPS >= 0) lastTickLength = 0;
16842         whiteTimeRemaining -= lastTickLength;
16843         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16844     } else {
16845         if(blackNPS >= 0) lastTickLength = 0;
16846         blackTimeRemaining -= lastTickLength;
16847         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16848     }
16849     CheckFlags();
16850 }
16851
16852 /* Start clock of player on move.  Time may have been reset, so
16853    if clock is already running, stop and restart it. */
16854 void
16855 StartClocks ()
16856 {
16857     (void) StopClockTimer(); /* in case it was running already */
16858     DisplayBothClocks();
16859     if (CheckFlags()) return;
16860
16861     if (!appData.clockMode) return;
16862     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16863
16864     GetTimeMark(&tickStartTM);
16865     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16866       whiteTimeRemaining : blackTimeRemaining);
16867
16868    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16869     whiteNPS = blackNPS = -1;
16870     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16871        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16872         whiteNPS = first.nps;
16873     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16874        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16875         blackNPS = first.nps;
16876     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16877         whiteNPS = second.nps;
16878     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16879         blackNPS = second.nps;
16880     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16881
16882     StartClockTimer(intendedTickLength);
16883 }
16884
16885 char *
16886 TimeString (long ms)
16887 {
16888     long second, minute, hour, day;
16889     char *sign = "";
16890     static char buf[32];
16891
16892     if (ms > 0 && ms <= 9900) {
16893       /* convert milliseconds to tenths, rounding up */
16894       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16895
16896       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16897       return buf;
16898     }
16899
16900     /* convert milliseconds to seconds, rounding up */
16901     /* use floating point to avoid strangeness of integer division
16902        with negative dividends on many machines */
16903     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16904
16905     if (second < 0) {
16906         sign = "-";
16907         second = -second;
16908     }
16909
16910     day = second / (60 * 60 * 24);
16911     second = second % (60 * 60 * 24);
16912     hour = second / (60 * 60);
16913     second = second % (60 * 60);
16914     minute = second / 60;
16915     second = second % 60;
16916
16917     if (day > 0)
16918       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16919               sign, day, hour, minute, second);
16920     else if (hour > 0)
16921       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16922     else
16923       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16924
16925     return buf;
16926 }
16927
16928
16929 /*
16930  * This is necessary because some C libraries aren't ANSI C compliant yet.
16931  */
16932 char *
16933 StrStr (char *string, char *match)
16934 {
16935     int i, length;
16936
16937     length = strlen(match);
16938
16939     for (i = strlen(string) - length; i >= 0; i--, string++)
16940       if (!strncmp(match, string, length))
16941         return string;
16942
16943     return NULL;
16944 }
16945
16946 char *
16947 StrCaseStr (char *string, char *match)
16948 {
16949     int i, j, length;
16950
16951     length = strlen(match);
16952
16953     for (i = strlen(string) - length; i >= 0; i--, string++) {
16954         for (j = 0; j < length; j++) {
16955             if (ToLower(match[j]) != ToLower(string[j]))
16956               break;
16957         }
16958         if (j == length) return string;
16959     }
16960
16961     return NULL;
16962 }
16963
16964 #ifndef _amigados
16965 int
16966 StrCaseCmp (char *s1, char *s2)
16967 {
16968     char c1, c2;
16969
16970     for (;;) {
16971         c1 = ToLower(*s1++);
16972         c2 = ToLower(*s2++);
16973         if (c1 > c2) return 1;
16974         if (c1 < c2) return -1;
16975         if (c1 == NULLCHAR) return 0;
16976     }
16977 }
16978
16979
16980 int
16981 ToLower (int c)
16982 {
16983     return isupper(c) ? tolower(c) : c;
16984 }
16985
16986
16987 int
16988 ToUpper (int c)
16989 {
16990     return islower(c) ? toupper(c) : c;
16991 }
16992 #endif /* !_amigados    */
16993
16994 char *
16995 StrSave (char *s)
16996 {
16997   char *ret;
16998
16999   if ((ret = (char *) malloc(strlen(s) + 1)))
17000     {
17001       safeStrCpy(ret, s, strlen(s)+1);
17002     }
17003   return ret;
17004 }
17005
17006 char *
17007 StrSavePtr (char *s, char **savePtr)
17008 {
17009     if (*savePtr) {
17010         free(*savePtr);
17011     }
17012     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17013       safeStrCpy(*savePtr, s, strlen(s)+1);
17014     }
17015     return(*savePtr);
17016 }
17017
17018 char *
17019 PGNDate ()
17020 {
17021     time_t clock;
17022     struct tm *tm;
17023     char buf[MSG_SIZ];
17024
17025     clock = time((time_t *)NULL);
17026     tm = localtime(&clock);
17027     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17028             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17029     return StrSave(buf);
17030 }
17031
17032
17033 char *
17034 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17035 {
17036     int i, j, fromX, fromY, toX, toY;
17037     int whiteToPlay;
17038     char buf[MSG_SIZ];
17039     char *p, *q;
17040     int emptycount;
17041     ChessSquare piece;
17042
17043     whiteToPlay = (gameMode == EditPosition) ?
17044       !blackPlaysFirst : (move % 2 == 0);
17045     p = buf;
17046
17047     /* Piece placement data */
17048     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17049         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17050         emptycount = 0;
17051         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17052             if (boards[move][i][j] == EmptySquare) {
17053                 emptycount++;
17054             } else { ChessSquare piece = boards[move][i][j];
17055                 if (emptycount > 0) {
17056                     if(emptycount<10) /* [HGM] can be >= 10 */
17057                         *p++ = '0' + emptycount;
17058                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17059                     emptycount = 0;
17060                 }
17061                 if(PieceToChar(piece) == '+') {
17062                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17063                     *p++ = '+';
17064                     piece = (ChessSquare)(DEMOTED piece);
17065                 }
17066                 *p++ = PieceToChar(piece);
17067                 if(p[-1] == '~') {
17068                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17069                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17070                     *p++ = '~';
17071                 }
17072             }
17073         }
17074         if (emptycount > 0) {
17075             if(emptycount<10) /* [HGM] can be >= 10 */
17076                 *p++ = '0' + emptycount;
17077             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17078             emptycount = 0;
17079         }
17080         *p++ = '/';
17081     }
17082     *(p - 1) = ' ';
17083
17084     /* [HGM] print Crazyhouse or Shogi holdings */
17085     if( gameInfo.holdingsWidth ) {
17086         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17087         q = p;
17088         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17089             piece = boards[move][i][BOARD_WIDTH-1];
17090             if( piece != EmptySquare )
17091               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17092                   *p++ = PieceToChar(piece);
17093         }
17094         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17095             piece = boards[move][BOARD_HEIGHT-i-1][0];
17096             if( piece != EmptySquare )
17097               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17098                   *p++ = PieceToChar(piece);
17099         }
17100
17101         if( q == p ) *p++ = '-';
17102         *p++ = ']';
17103         *p++ = ' ';
17104     }
17105
17106     /* Active color */
17107     *p++ = whiteToPlay ? 'w' : 'b';
17108     *p++ = ' ';
17109
17110   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17111     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17112   } else {
17113   if(nrCastlingRights) {
17114      q = p;
17115      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17116        /* [HGM] write directly from rights */
17117            if(boards[move][CASTLING][2] != NoRights &&
17118               boards[move][CASTLING][0] != NoRights   )
17119                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17120            if(boards[move][CASTLING][2] != NoRights &&
17121               boards[move][CASTLING][1] != NoRights   )
17122                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17123            if(boards[move][CASTLING][5] != NoRights &&
17124               boards[move][CASTLING][3] != NoRights   )
17125                 *p++ = boards[move][CASTLING][3] + AAA;
17126            if(boards[move][CASTLING][5] != NoRights &&
17127               boards[move][CASTLING][4] != NoRights   )
17128                 *p++ = boards[move][CASTLING][4] + AAA;
17129      } else {
17130
17131         /* [HGM] write true castling rights */
17132         if( nrCastlingRights == 6 ) {
17133             int q, k=0;
17134             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17135                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17136             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17137                  boards[move][CASTLING][2] != NoRights  );
17138             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17139                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17140                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17141                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17142                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17143             }
17144             if(q) *p++ = 'Q';
17145             k = 0;
17146             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17147                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17148             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17149                  boards[move][CASTLING][5] != NoRights  );
17150             if(gameInfo.variant == VariantSChess) {
17151                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17152                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17153                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17154                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17155             }
17156             if(q) *p++ = 'q';
17157         }
17158      }
17159      if (q == p) *p++ = '-'; /* No castling rights */
17160      *p++ = ' ';
17161   }
17162
17163   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17164      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17165      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17166     /* En passant target square */
17167     if (move > backwardMostMove) {
17168         fromX = moveList[move - 1][0] - AAA;
17169         fromY = moveList[move - 1][1] - ONE;
17170         toX = moveList[move - 1][2] - AAA;
17171         toY = moveList[move - 1][3] - ONE;
17172         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17173             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17174             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17175             fromX == toX) {
17176             /* 2-square pawn move just happened */
17177             *p++ = toX + AAA;
17178             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17179         } else {
17180             *p++ = '-';
17181         }
17182     } else if(move == backwardMostMove) {
17183         // [HGM] perhaps we should always do it like this, and forget the above?
17184         if((signed char)boards[move][EP_STATUS] >= 0) {
17185             *p++ = boards[move][EP_STATUS] + AAA;
17186             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17187         } else {
17188             *p++ = '-';
17189         }
17190     } else {
17191         *p++ = '-';
17192     }
17193     *p++ = ' ';
17194   }
17195   }
17196
17197     if(moveCounts)
17198     {   int i = 0, j=move;
17199
17200         /* [HGM] find reversible plies */
17201         if (appData.debugMode) { int k;
17202             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17203             for(k=backwardMostMove; k<=forwardMostMove; k++)
17204                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17205
17206         }
17207
17208         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17209         if( j == backwardMostMove ) i += initialRulePlies;
17210         sprintf(p, "%d ", i);
17211         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17212
17213         /* Fullmove number */
17214         sprintf(p, "%d", (move / 2) + 1);
17215     } else *--p = NULLCHAR;
17216
17217     return StrSave(buf);
17218 }
17219
17220 Boolean
17221 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17222 {
17223     int i, j;
17224     char *p, c;
17225     int emptycount, virgin[BOARD_FILES];
17226     ChessSquare piece;
17227
17228     p = fen;
17229
17230     /* [HGM] by default clear Crazyhouse holdings, if present */
17231     if(gameInfo.holdingsWidth) {
17232        for(i=0; i<BOARD_HEIGHT; i++) {
17233            board[i][0]             = EmptySquare; /* black holdings */
17234            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17235            board[i][1]             = (ChessSquare) 0; /* black counts */
17236            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17237        }
17238     }
17239
17240     /* Piece placement data */
17241     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17242         j = 0;
17243         for (;;) {
17244             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17245                 if (*p == '/') p++;
17246                 emptycount = gameInfo.boardWidth - j;
17247                 while (emptycount--)
17248                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17249                 break;
17250 #if(BOARD_FILES >= 10)
17251             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17252                 p++; emptycount=10;
17253                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17254                 while (emptycount--)
17255                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17256 #endif
17257             } else if (isdigit(*p)) {
17258                 emptycount = *p++ - '0';
17259                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17260                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17261                 while (emptycount--)
17262                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17263             } else if (*p == '+' || isalpha(*p)) {
17264                 if (j >= gameInfo.boardWidth) return FALSE;
17265                 if(*p=='+') {
17266                     piece = CharToPiece(*++p);
17267                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17268                     piece = (ChessSquare) (PROMOTED piece ); p++;
17269                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17270                 } else piece = CharToPiece(*p++);
17271
17272                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17273                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17274                     piece = (ChessSquare) (PROMOTED piece);
17275                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17276                     p++;
17277                 }
17278                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17279             } else {
17280                 return FALSE;
17281             }
17282         }
17283     }
17284     while (*p == '/' || *p == ' ') p++;
17285
17286     /* [HGM] look for Crazyhouse holdings here */
17287     while(*p==' ') p++;
17288     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17289         if(*p == '[') p++;
17290         if(*p == '-' ) p++; /* empty holdings */ else {
17291             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17292             /* if we would allow FEN reading to set board size, we would   */
17293             /* have to add holdings and shift the board read so far here   */
17294             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17295                 p++;
17296                 if((int) piece >= (int) BlackPawn ) {
17297                     i = (int)piece - (int)BlackPawn;
17298                     i = PieceToNumber((ChessSquare)i);
17299                     if( i >= gameInfo.holdingsSize ) return FALSE;
17300                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17301                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17302                 } else {
17303                     i = (int)piece - (int)WhitePawn;
17304                     i = PieceToNumber((ChessSquare)i);
17305                     if( i >= gameInfo.holdingsSize ) return FALSE;
17306                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17307                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17308                 }
17309             }
17310         }
17311         if(*p == ']') p++;
17312     }
17313
17314     while(*p == ' ') p++;
17315
17316     /* Active color */
17317     c = *p++;
17318     if(appData.colorNickNames) {
17319       if( c == appData.colorNickNames[0] ) c = 'w'; else
17320       if( c == appData.colorNickNames[1] ) c = 'b';
17321     }
17322     switch (c) {
17323       case 'w':
17324         *blackPlaysFirst = FALSE;
17325         break;
17326       case 'b':
17327         *blackPlaysFirst = TRUE;
17328         break;
17329       default:
17330         return FALSE;
17331     }
17332
17333     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17334     /* return the extra info in global variiables             */
17335
17336     /* set defaults in case FEN is incomplete */
17337     board[EP_STATUS] = EP_UNKNOWN;
17338     for(i=0; i<nrCastlingRights; i++ ) {
17339         board[CASTLING][i] =
17340             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17341     }   /* assume possible unless obviously impossible */
17342     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17343     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17344     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17345                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17346     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17347     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17348     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17349                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17350     FENrulePlies = 0;
17351
17352     while(*p==' ') p++;
17353     if(nrCastlingRights) {
17354       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17355       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17356           /* castling indicator present, so default becomes no castlings */
17357           for(i=0; i<nrCastlingRights; i++ ) {
17358                  board[CASTLING][i] = NoRights;
17359           }
17360       }
17361       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17362              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17363              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17364              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17365         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17366
17367         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17368             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17369             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17370         }
17371         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17372             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17373         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17374                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17375         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17376                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17377         switch(c) {
17378           case'K':
17379               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17380               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17381               board[CASTLING][2] = whiteKingFile;
17382               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17383               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17384               break;
17385           case'Q':
17386               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17387               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17388               board[CASTLING][2] = whiteKingFile;
17389               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17390               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17391               break;
17392           case'k':
17393               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17394               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17395               board[CASTLING][5] = blackKingFile;
17396               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17397               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17398               break;
17399           case'q':
17400               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17401               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17402               board[CASTLING][5] = blackKingFile;
17403               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17404               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17405           case '-':
17406               break;
17407           default: /* FRC castlings */
17408               if(c >= 'a') { /* black rights */
17409                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17410                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17411                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17412                   if(i == BOARD_RGHT) break;
17413                   board[CASTLING][5] = i;
17414                   c -= AAA;
17415                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17416                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17417                   if(c > i)
17418                       board[CASTLING][3] = c;
17419                   else
17420                       board[CASTLING][4] = c;
17421               } else { /* white rights */
17422                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17423                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17424                     if(board[0][i] == WhiteKing) break;
17425                   if(i == BOARD_RGHT) break;
17426                   board[CASTLING][2] = i;
17427                   c -= AAA - 'a' + 'A';
17428                   if(board[0][c] >= WhiteKing) break;
17429                   if(c > i)
17430                       board[CASTLING][0] = c;
17431                   else
17432                       board[CASTLING][1] = c;
17433               }
17434         }
17435       }
17436       for(i=0; i<nrCastlingRights; i++)
17437         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17438       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17439     if (appData.debugMode) {
17440         fprintf(debugFP, "FEN castling rights:");
17441         for(i=0; i<nrCastlingRights; i++)
17442         fprintf(debugFP, " %d", board[CASTLING][i]);
17443         fprintf(debugFP, "\n");
17444     }
17445
17446       while(*p==' ') p++;
17447     }
17448
17449     /* read e.p. field in games that know e.p. capture */
17450     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17451        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17452        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17453       if(*p=='-') {
17454         p++; board[EP_STATUS] = EP_NONE;
17455       } else {
17456          char c = *p++ - AAA;
17457
17458          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17459          if(*p >= '0' && *p <='9') p++;
17460          board[EP_STATUS] = c;
17461       }
17462     }
17463
17464
17465     if(sscanf(p, "%d", &i) == 1) {
17466         FENrulePlies = i; /* 50-move ply counter */
17467         /* (The move number is still ignored)    */
17468     }
17469
17470     return TRUE;
17471 }
17472
17473 void
17474 EditPositionPasteFEN (char *fen)
17475 {
17476   if (fen != NULL) {
17477     Board initial_position;
17478
17479     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17480       DisplayError(_("Bad FEN position in clipboard"), 0);
17481       return ;
17482     } else {
17483       int savedBlackPlaysFirst = blackPlaysFirst;
17484       EditPositionEvent();
17485       blackPlaysFirst = savedBlackPlaysFirst;
17486       CopyBoard(boards[0], initial_position);
17487       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17488       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17489       DisplayBothClocks();
17490       DrawPosition(FALSE, boards[currentMove]);
17491     }
17492   }
17493 }
17494
17495 static char cseq[12] = "\\   ";
17496
17497 Boolean
17498 set_cont_sequence (char *new_seq)
17499 {
17500     int len;
17501     Boolean ret;
17502
17503     // handle bad attempts to set the sequence
17504         if (!new_seq)
17505                 return 0; // acceptable error - no debug
17506
17507     len = strlen(new_seq);
17508     ret = (len > 0) && (len < sizeof(cseq));
17509     if (ret)
17510       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17511     else if (appData.debugMode)
17512       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17513     return ret;
17514 }
17515
17516 /*
17517     reformat a source message so words don't cross the width boundary.  internal
17518     newlines are not removed.  returns the wrapped size (no null character unless
17519     included in source message).  If dest is NULL, only calculate the size required
17520     for the dest buffer.  lp argument indicats line position upon entry, and it's
17521     passed back upon exit.
17522 */
17523 int
17524 wrap (char *dest, char *src, int count, int width, int *lp)
17525 {
17526     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17527
17528     cseq_len = strlen(cseq);
17529     old_line = line = *lp;
17530     ansi = len = clen = 0;
17531
17532     for (i=0; i < count; i++)
17533     {
17534         if (src[i] == '\033')
17535             ansi = 1;
17536
17537         // if we hit the width, back up
17538         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17539         {
17540             // store i & len in case the word is too long
17541             old_i = i, old_len = len;
17542
17543             // find the end of the last word
17544             while (i && src[i] != ' ' && src[i] != '\n')
17545             {
17546                 i--;
17547                 len--;
17548             }
17549
17550             // word too long?  restore i & len before splitting it
17551             if ((old_i-i+clen) >= width)
17552             {
17553                 i = old_i;
17554                 len = old_len;
17555             }
17556
17557             // extra space?
17558             if (i && src[i-1] == ' ')
17559                 len--;
17560
17561             if (src[i] != ' ' && src[i] != '\n')
17562             {
17563                 i--;
17564                 if (len)
17565                     len--;
17566             }
17567
17568             // now append the newline and continuation sequence
17569             if (dest)
17570                 dest[len] = '\n';
17571             len++;
17572             if (dest)
17573                 strncpy(dest+len, cseq, cseq_len);
17574             len += cseq_len;
17575             line = cseq_len;
17576             clen = cseq_len;
17577             continue;
17578         }
17579
17580         if (dest)
17581             dest[len] = src[i];
17582         len++;
17583         if (!ansi)
17584             line++;
17585         if (src[i] == '\n')
17586             line = 0;
17587         if (src[i] == 'm')
17588             ansi = 0;
17589     }
17590     if (dest && appData.debugMode)
17591     {
17592         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17593             count, width, line, len, *lp);
17594         show_bytes(debugFP, src, count);
17595         fprintf(debugFP, "\ndest: ");
17596         show_bytes(debugFP, dest, len);
17597         fprintf(debugFP, "\n");
17598     }
17599     *lp = dest ? line : old_line;
17600
17601     return len;
17602 }
17603
17604 // [HGM] vari: routines for shelving variations
17605 Boolean modeRestore = FALSE;
17606
17607 void
17608 PushInner (int firstMove, int lastMove)
17609 {
17610         int i, j, nrMoves = lastMove - firstMove;
17611
17612         // push current tail of game on stack
17613         savedResult[storedGames] = gameInfo.result;
17614         savedDetails[storedGames] = gameInfo.resultDetails;
17615         gameInfo.resultDetails = NULL;
17616         savedFirst[storedGames] = firstMove;
17617         savedLast [storedGames] = lastMove;
17618         savedFramePtr[storedGames] = framePtr;
17619         framePtr -= nrMoves; // reserve space for the boards
17620         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17621             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17622             for(j=0; j<MOVE_LEN; j++)
17623                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17624             for(j=0; j<2*MOVE_LEN; j++)
17625                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17626             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17627             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17628             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17629             pvInfoList[firstMove+i-1].depth = 0;
17630             commentList[framePtr+i] = commentList[firstMove+i];
17631             commentList[firstMove+i] = NULL;
17632         }
17633
17634         storedGames++;
17635         forwardMostMove = firstMove; // truncate game so we can start variation
17636 }
17637
17638 void
17639 PushTail (int firstMove, int lastMove)
17640 {
17641         if(appData.icsActive) { // only in local mode
17642                 forwardMostMove = currentMove; // mimic old ICS behavior
17643                 return;
17644         }
17645         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17646
17647         PushInner(firstMove, lastMove);
17648         if(storedGames == 1) GreyRevert(FALSE);
17649         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17650 }
17651
17652 void
17653 PopInner (Boolean annotate)
17654 {
17655         int i, j, nrMoves;
17656         char buf[8000], moveBuf[20];
17657
17658         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17659         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17660         nrMoves = savedLast[storedGames] - currentMove;
17661         if(annotate) {
17662                 int cnt = 10;
17663                 if(!WhiteOnMove(currentMove))
17664                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17665                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17666                 for(i=currentMove; i<forwardMostMove; i++) {
17667                         if(WhiteOnMove(i))
17668                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17669                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17670                         strcat(buf, moveBuf);
17671                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17672                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17673                 }
17674                 strcat(buf, ")");
17675         }
17676         for(i=1; i<=nrMoves; i++) { // copy last variation back
17677             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17678             for(j=0; j<MOVE_LEN; j++)
17679                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17680             for(j=0; j<2*MOVE_LEN; j++)
17681                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17682             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17683             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17684             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17685             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17686             commentList[currentMove+i] = commentList[framePtr+i];
17687             commentList[framePtr+i] = NULL;
17688         }
17689         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17690         framePtr = savedFramePtr[storedGames];
17691         gameInfo.result = savedResult[storedGames];
17692         if(gameInfo.resultDetails != NULL) {
17693             free(gameInfo.resultDetails);
17694       }
17695         gameInfo.resultDetails = savedDetails[storedGames];
17696         forwardMostMove = currentMove + nrMoves;
17697 }
17698
17699 Boolean
17700 PopTail (Boolean annotate)
17701 {
17702         if(appData.icsActive) return FALSE; // only in local mode
17703         if(!storedGames) return FALSE; // sanity
17704         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17705
17706         PopInner(annotate);
17707         if(currentMove < forwardMostMove) ForwardEvent(); else
17708         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17709
17710         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17711         return TRUE;
17712 }
17713
17714 void
17715 CleanupTail ()
17716 {       // remove all shelved variations
17717         int i;
17718         for(i=0; i<storedGames; i++) {
17719             if(savedDetails[i])
17720                 free(savedDetails[i]);
17721             savedDetails[i] = NULL;
17722         }
17723         for(i=framePtr; i<MAX_MOVES; i++) {
17724                 if(commentList[i]) free(commentList[i]);
17725                 commentList[i] = NULL;
17726         }
17727         framePtr = MAX_MOVES-1;
17728         storedGames = 0;
17729 }
17730
17731 void
17732 LoadVariation (int index, char *text)
17733 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17734         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17735         int level = 0, move;
17736
17737         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17738         // first find outermost bracketing variation
17739         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17740             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17741                 if(*p == '{') wait = '}'; else
17742                 if(*p == '[') wait = ']'; else
17743                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17744                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17745             }
17746             if(*p == wait) wait = NULLCHAR; // closing ]} found
17747             p++;
17748         }
17749         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17750         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17751         end[1] = NULLCHAR; // clip off comment beyond variation
17752         ToNrEvent(currentMove-1);
17753         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17754         // kludge: use ParsePV() to append variation to game
17755         move = currentMove;
17756         ParsePV(start, TRUE, TRUE);
17757         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17758         ClearPremoveHighlights();
17759         CommentPopDown();
17760         ToNrEvent(currentMove+1);
17761 }
17762
17763 void
17764 LoadTheme ()
17765 {
17766     char *p, *q, buf[MSG_SIZ];
17767     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17768         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17769         ParseArgsFromString(buf);
17770         ActivateTheme(TRUE); // also redo colors
17771         return;
17772     }
17773     p = nickName;
17774     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17775     {
17776         int len;
17777         q = appData.themeNames;
17778         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17779       if(appData.useBitmaps) {
17780         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17781                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17782                 appData.liteBackTextureMode,
17783                 appData.darkBackTextureMode );
17784       } else {
17785         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17786                 Col2Text(2),   // lightSquareColor
17787                 Col2Text(3) ); // darkSquareColor
17788       }
17789       if(appData.useBorder) {
17790         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17791                 appData.border);
17792       } else {
17793         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17794       }
17795       if(appData.useFont) {
17796         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17797                 appData.renderPiecesWithFont,
17798                 appData.fontToPieceTable,
17799                 Col2Text(9),    // appData.fontBackColorWhite
17800                 Col2Text(10) ); // appData.fontForeColorBlack
17801       } else {
17802         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17803                 appData.pieceDirectory);
17804         if(!appData.pieceDirectory[0])
17805           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17806                 Col2Text(0),   // whitePieceColor
17807                 Col2Text(1) ); // blackPieceColor
17808       }
17809       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17810                 Col2Text(4),   // highlightSquareColor
17811                 Col2Text(5) ); // premoveHighlightColor
17812         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17813         if(insert != q) insert[-1] = NULLCHAR;
17814         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17815         if(q)   free(q);
17816     }
17817     ActivateTheme(FALSE);
17818 }