Fix GUI book after setup position
[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 VariantGrand:
392     flags &= ~F_ALL_CASTLE_OK;
393     break;
394   default:
395     break;
396   }
397   return flags;
398 }
399
400 FILE *gameFileFP, *debugFP, *serverFP;
401 char *currentDebugFile; // [HGM] debug split: to remember name
402
403 /*
404     [AS] Note: sometimes, the sscanf() function is used to parse the input
405     into a fixed-size buffer. Because of this, we must be prepared to
406     receive strings as long as the size of the input buffer, which is currently
407     set to 4K for Windows and 8K for the rest.
408     So, we must either allocate sufficiently large buffers here, or
409     reduce the size of the input buffer in the input reading part.
410 */
411
412 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
413 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
414 char thinkOutput1[MSG_SIZ*10];
415
416 ChessProgramState first, second, pairing;
417
418 /* premove variables */
419 int premoveToX = 0;
420 int premoveToY = 0;
421 int premoveFromX = 0;
422 int premoveFromY = 0;
423 int premovePromoChar = 0;
424 int gotPremove = 0;
425 Boolean alarmSounded;
426 /* end premove variables */
427
428 char *ics_prefix = "$";
429 enum ICS_TYPE ics_type = ICS_GENERIC;
430
431 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
432 int pauseExamForwardMostMove = 0;
433 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
434 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
435 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
436 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
437 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
438 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
439 int whiteFlag = FALSE, blackFlag = FALSE;
440 int userOfferedDraw = FALSE;
441 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
442 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
443 int cmailMoveType[CMAIL_MAX_GAMES];
444 long ics_clock_paused = 0;
445 ProcRef icsPR = NoProc, cmailPR = NoProc;
446 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
447 GameMode gameMode = BeginningOfGame;
448 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
449 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
450 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
451 int hiddenThinkOutputState = 0; /* [AS] */
452 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
453 int adjudicateLossPlies = 6;
454 char white_holding[64], black_holding[64];
455 TimeMark lastNodeCountTime;
456 long lastNodeCount=0;
457 int shiftKey, controlKey; // [HGM] set by mouse handler
458
459 int have_sent_ICS_logon = 0;
460 int movesPerSession;
461 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
462 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
463 Boolean adjustedClock;
464 long timeControl_2; /* [AS] Allow separate time controls */
465 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
466 long timeRemaining[2][MAX_MOVES];
467 int matchGame = 0, nextGame = 0, roundNr = 0;
468 Boolean waitingForGame = FALSE, startingEngine = FALSE;
469 TimeMark programStartTime, pauseStart;
470 char ics_handle[MSG_SIZ];
471 int have_set_title = 0;
472
473 /* animateTraining preserves the state of appData.animate
474  * when Training mode is activated. This allows the
475  * response to be animated when appData.animate == TRUE and
476  * appData.animateDragging == TRUE.
477  */
478 Boolean animateTraining;
479
480 GameInfo gameInfo;
481
482 AppData appData;
483
484 Board boards[MAX_MOVES];
485 /* [HGM] Following 7 needed for accurate legality tests: */
486 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
487 signed char  initialRights[BOARD_FILES];
488 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
489 int   initialRulePlies, FENrulePlies;
490 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
491 int loadFlag = 0;
492 Boolean shuffleOpenings;
493 int mute; // mute all sounds
494
495 // [HGM] vari: next 12 to save and restore variations
496 #define MAX_VARIATIONS 10
497 int framePtr = MAX_MOVES-1; // points to free stack entry
498 int storedGames = 0;
499 int savedFirst[MAX_VARIATIONS];
500 int savedLast[MAX_VARIATIONS];
501 int savedFramePtr[MAX_VARIATIONS];
502 char *savedDetails[MAX_VARIATIONS];
503 ChessMove savedResult[MAX_VARIATIONS];
504
505 void PushTail P((int firstMove, int lastMove));
506 Boolean PopTail P((Boolean annotate));
507 void PushInner P((int firstMove, int lastMove));
508 void PopInner P((Boolean annotate));
509 void CleanupTail P((void));
510
511 ChessSquare  FIDEArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackBishop, BlackKnight, BlackRook }
516 };
517
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522         BlackKing, BlackKing, BlackKnight, BlackRook }
523 };
524
525 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528     { BlackRook, BlackMan, BlackBishop, BlackQueen,
529         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
530 };
531
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
537 };
538
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
544 };
545
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
551 };
552
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556     { BlackRook, BlackKnight, BlackMan, BlackFerz,
557         BlackKing, BlackMan, BlackKnight, BlackRook }
558 };
559
560
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
567 };
568
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
581 };
582
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
588 };
589
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
595 };
596
597 ChessSquare GrandArray[2][BOARD_FILES] = {
598     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
599         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
600     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
601         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
602 };
603
604 #ifdef GOTHIC
605 ChessSquare GothicArray[2][BOARD_FILES] = {
606     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
607         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
608     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
609         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
610 };
611 #else // !GOTHIC
612 #define GothicArray CapablancaArray
613 #endif // !GOTHIC
614
615 #ifdef FALCON
616 ChessSquare FalconArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
618         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
620         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
621 };
622 #else // !FALCON
623 #define FalconArray CapablancaArray
624 #endif // !FALCON
625
626 #else // !(BOARD_FILES>=10)
627 #define XiangqiPosition FIDEArray
628 #define CapablancaArray FIDEArray
629 #define GothicArray FIDEArray
630 #define GreatArray FIDEArray
631 #endif // !(BOARD_FILES>=10)
632
633 #if (BOARD_FILES>=12)
634 ChessSquare CourierArray[2][BOARD_FILES] = {
635     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
636         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
637     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
638         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
639 };
640 #else // !(BOARD_FILES>=12)
641 #define CourierArray CapablancaArray
642 #endif // !(BOARD_FILES>=12)
643
644
645 Board initialPosition;
646
647
648 /* Convert str to a rating. Checks for special cases of "----",
649
650    "++++", etc. Also strips ()'s */
651 int
652 string_to_rating (char *str)
653 {
654   while(*str && !isdigit(*str)) ++str;
655   if (!*str)
656     return 0;   /* One of the special "no rating" cases */
657   else
658     return atoi(str);
659 }
660
661 void
662 ClearProgramStats ()
663 {
664     /* Init programStats */
665     programStats.movelist[0] = 0;
666     programStats.depth = 0;
667     programStats.nr_moves = 0;
668     programStats.moves_left = 0;
669     programStats.nodes = 0;
670     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
671     programStats.score = 0;
672     programStats.got_only_move = 0;
673     programStats.got_fail = 0;
674     programStats.line_is_book = 0;
675 }
676
677 void
678 CommonEngineInit ()
679 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
680     if (appData.firstPlaysBlack) {
681         first.twoMachinesColor = "black\n";
682         second.twoMachinesColor = "white\n";
683     } else {
684         first.twoMachinesColor = "white\n";
685         second.twoMachinesColor = "black\n";
686     }
687
688     first.other = &second;
689     second.other = &first;
690
691     { float norm = 1;
692         if(appData.timeOddsMode) {
693             norm = appData.timeOdds[0];
694             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
695         }
696         first.timeOdds  = appData.timeOdds[0]/norm;
697         second.timeOdds = appData.timeOdds[1]/norm;
698     }
699
700     if(programVersion) free(programVersion);
701     if (appData.noChessProgram) {
702         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
703         sprintf(programVersion, "%s", PACKAGE_STRING);
704     } else {
705       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
706       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
707       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708     }
709 }
710
711 void
712 UnloadEngine (ChessProgramState *cps)
713 {
714         /* Kill off first chess program */
715         if (cps->isr != NULL)
716           RemoveInputSource(cps->isr);
717         cps->isr = NULL;
718
719         if (cps->pr != NoProc) {
720             ExitAnalyzeMode();
721             DoSleep( appData.delayBeforeQuit );
722             SendToProgram("quit\n", cps);
723             DoSleep( appData.delayAfterQuit );
724             DestroyChildProcess(cps->pr, cps->useSigterm);
725         }
726         cps->pr = NoProc;
727         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 }
729
730 void
731 ClearOptions (ChessProgramState *cps)
732 {
733     int i;
734     cps->nrOptions = cps->comboCnt = 0;
735     for(i=0; i<MAX_OPTIONS; i++) {
736         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
737         cps->option[i].textValue = 0;
738     }
739 }
740
741 char *engineNames[] = {
742   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
743      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 N_("first"),
745   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
746      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
747 N_("second")
748 };
749
750 void
751 InitEngine (ChessProgramState *cps, int n)
752 {   // [HGM] all engine initialiation put in a function that does one engine
753
754     ClearOptions(cps);
755
756     cps->which = engineNames[n];
757     cps->maybeThinking = FALSE;
758     cps->pr = NoProc;
759     cps->isr = NULL;
760     cps->sendTime = 2;
761     cps->sendDrawOffers = 1;
762
763     cps->program = appData.chessProgram[n];
764     cps->host = appData.host[n];
765     cps->dir = appData.directory[n];
766     cps->initString = appData.engInitString[n];
767     cps->computerString = appData.computerString[n];
768     cps->useSigint  = TRUE;
769     cps->useSigterm = TRUE;
770     cps->reuse = appData.reuse[n];
771     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
772     cps->useSetboard = FALSE;
773     cps->useSAN = FALSE;
774     cps->usePing = FALSE;
775     cps->lastPing = 0;
776     cps->lastPong = 0;
777     cps->usePlayother = FALSE;
778     cps->useColors = TRUE;
779     cps->useUsermove = FALSE;
780     cps->sendICS = FALSE;
781     cps->sendName = appData.icsActive;
782     cps->sdKludge = FALSE;
783     cps->stKludge = FALSE;
784     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
785     TidyProgramName(cps->program, cps->host, cps->tidy);
786     cps->matchWins = 0;
787     ASSIGN(cps->variants, appData.variant);
788     cps->analysisSupport = 2; /* detect */
789     cps->analyzing = FALSE;
790     cps->initDone = FALSE;
791     cps->reload = FALSE;
792
793     /* New features added by Tord: */
794     cps->useFEN960 = FALSE;
795     cps->useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     cps->fenOverride  = appData.fenOverride[n];
798
799     /* [HGM] time odds: set factor for each machine */
800     cps->timeOdds  = appData.timeOdds[n];
801
802     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
803     cps->accumulateTC = appData.accumulateTC[n];
804     cps->maxNrOfSessions = 1;
805
806     /* [HGM] debug */
807     cps->debug = FALSE;
808
809     cps->supportsNPS = UNKNOWN;
810     cps->memSize = FALSE;
811     cps->maxCores = FALSE;
812     ASSIGN(cps->egtFormats, "");
813
814     /* [HGM] options */
815     cps->optionSettings  = appData.engOptions[n];
816
817     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
818     cps->isUCI = appData.isUCI[n]; /* [AS] */
819     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
820
821     if (appData.protocolVersion[n] > PROTOVER
822         || appData.protocolVersion[n] < 1)
823       {
824         char buf[MSG_SIZ];
825         int len;
826
827         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
828                        appData.protocolVersion[n]);
829         if( (len >= MSG_SIZ) && appData.debugMode )
830           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
831
832         DisplayFatalError(buf, 0, 2);
833       }
834     else
835       {
836         cps->protocolVersion = appData.protocolVersion[n];
837       }
838
839     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
840     ParseFeatures(appData.featureDefaults, cps);
841 }
842
843 ChessProgramState *savCps;
844
845 GameMode oldMode;
846
847 void
848 LoadEngine ()
849 {
850     int i;
851     if(WaitForEngine(savCps, LoadEngine)) return;
852     CommonEngineInit(); // recalculate time odds
853     if(gameInfo.variant != StringToVariant(appData.variant)) {
854         // we changed variant when loading the engine; this forces us to reset
855         Reset(TRUE, savCps != &first);
856         oldMode = BeginningOfGame; // to prevent restoring old mode
857     }
858     InitChessProgram(savCps, FALSE);
859     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
860     DisplayMessage("", "");
861     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
862     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
863     ThawUI();
864     SetGNUMode();
865     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
866 }
867
868 void
869 ReplaceEngine (ChessProgramState *cps, int n)
870 {
871     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
872     keepInfo = 1;
873     if(oldMode != BeginningOfGame) EditGameEvent();
874     keepInfo = 0;
875     UnloadEngine(cps);
876     appData.noChessProgram = FALSE;
877     appData.clockMode = TRUE;
878     InitEngine(cps, n);
879     UpdateLogos(TRUE);
880     if(n) return; // only startup first engine immediately; second can wait
881     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
882     LoadEngine();
883 }
884
885 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
886 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
887
888 static char resetOptions[] =
889         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
890         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
891         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
892         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
893
894 void
895 FloatToFront(char **list, char *engineLine)
896 {
897     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
898     int i=0;
899     if(appData.recentEngines <= 0) return;
900     TidyProgramName(engineLine, "localhost", tidy+1);
901     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
902     strncpy(buf+1, *list, MSG_SIZ-50);
903     if(p = strstr(buf, tidy)) { // tidy name appears in list
904         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
905         while(*p++ = *++q); // squeeze out
906     }
907     strcat(tidy, buf+1); // put list behind tidy name
908     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
909     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
910     ASSIGN(*list, tidy+1);
911 }
912
913 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
914
915 void
916 Load (ChessProgramState *cps, int i)
917 {
918     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
919     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
920         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
921         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
922         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
923         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
924         appData.firstProtocolVersion = PROTOVER;
925         ParseArgsFromString(buf);
926         SwapEngines(i);
927         ReplaceEngine(cps, i);
928         FloatToFront(&appData.recentEngineList, engineLine);
929         return;
930     }
931     p = engineName;
932     while(q = strchr(p, SLASH)) p = q+1;
933     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
934     if(engineDir[0] != NULLCHAR) {
935         ASSIGN(appData.directory[i], engineDir); p = engineName;
936     } else if(p != engineName) { // derive directory from engine path, when not given
937         p[-1] = 0;
938         ASSIGN(appData.directory[i], engineName);
939         p[-1] = SLASH;
940         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
941     } else { ASSIGN(appData.directory[i], "."); }
942     if(params[0]) {
943         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
944         snprintf(command, MSG_SIZ, "%s %s", p, params);
945         p = command;
946     }
947     ASSIGN(appData.chessProgram[i], p);
948     appData.isUCI[i] = isUCI;
949     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
950     appData.hasOwnBookUCI[i] = hasBook;
951     if(!nickName[0]) useNick = FALSE;
952     if(useNick) ASSIGN(appData.pgnName[i], nickName);
953     if(addToList) {
954         int len;
955         char quote;
956         q = firstChessProgramNames;
957         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
958         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
959         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
960                         quote, p, quote, appData.directory[i],
961                         useNick ? " -fn \"" : "",
962                         useNick ? nickName : "",
963                         useNick ? "\"" : "",
964                         v1 ? " -firstProtocolVersion 1" : "",
965                         hasBook ? "" : " -fNoOwnBookUCI",
966                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
967                         storeVariant ? " -variant " : "",
968                         storeVariant ? VariantName(gameInfo.variant) : "");
969         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
970         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
971         if(insert != q) insert[-1] = NULLCHAR;
972         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
973         if(q)   free(q);
974         FloatToFront(&appData.recentEngineList, buf);
975     }
976     ReplaceEngine(cps, i);
977 }
978
979 void
980 InitTimeControls ()
981 {
982     int matched, min, sec;
983     /*
984      * Parse timeControl resource
985      */
986     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
987                           appData.movesPerSession)) {
988         char buf[MSG_SIZ];
989         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
990         DisplayFatalError(buf, 0, 2);
991     }
992
993     /*
994      * Parse searchTime resource
995      */
996     if (*appData.searchTime != NULLCHAR) {
997         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
998         if (matched == 1) {
999             searchTime = min * 60;
1000         } else if (matched == 2) {
1001             searchTime = min * 60 + sec;
1002         } else {
1003             char buf[MSG_SIZ];
1004             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1005             DisplayFatalError(buf, 0, 2);
1006         }
1007     }
1008 }
1009
1010 void
1011 InitBackEnd1 ()
1012 {
1013
1014     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1015     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1016
1017     GetTimeMark(&programStartTime);
1018     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1019     appData.seedBase = random() + (random()<<15);
1020     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1021
1022     ClearProgramStats();
1023     programStats.ok_to_send = 1;
1024     programStats.seen_stat = 0;
1025
1026     /*
1027      * Initialize game list
1028      */
1029     ListNew(&gameList);
1030
1031
1032     /*
1033      * Internet chess server status
1034      */
1035     if (appData.icsActive) {
1036         appData.matchMode = FALSE;
1037         appData.matchGames = 0;
1038 #if ZIPPY
1039         appData.noChessProgram = !appData.zippyPlay;
1040 #else
1041         appData.zippyPlay = FALSE;
1042         appData.zippyTalk = FALSE;
1043         appData.noChessProgram = TRUE;
1044 #endif
1045         if (*appData.icsHelper != NULLCHAR) {
1046             appData.useTelnet = TRUE;
1047             appData.telnetProgram = appData.icsHelper;
1048         }
1049     } else {
1050         appData.zippyTalk = appData.zippyPlay = FALSE;
1051     }
1052
1053     /* [AS] Initialize pv info list [HGM] and game state */
1054     {
1055         int i, j;
1056
1057         for( i=0; i<=framePtr; i++ ) {
1058             pvInfoList[i].depth = -1;
1059             boards[i][EP_STATUS] = EP_NONE;
1060             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1061         }
1062     }
1063
1064     InitTimeControls();
1065
1066     /* [AS] Adjudication threshold */
1067     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1068
1069     InitEngine(&first, 0);
1070     InitEngine(&second, 1);
1071     CommonEngineInit();
1072
1073     pairing.which = "pairing"; // pairing engine
1074     pairing.pr = NoProc;
1075     pairing.isr = NULL;
1076     pairing.program = appData.pairingEngine;
1077     pairing.host = "localhost";
1078     pairing.dir = ".";
1079
1080     if (appData.icsActive) {
1081         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1082     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1083         appData.clockMode = FALSE;
1084         first.sendTime = second.sendTime = 0;
1085     }
1086
1087 #if ZIPPY
1088     /* Override some settings from environment variables, for backward
1089        compatibility.  Unfortunately it's not feasible to have the env
1090        vars just set defaults, at least in xboard.  Ugh.
1091     */
1092     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1093       ZippyInit();
1094     }
1095 #endif
1096
1097     if (!appData.icsActive) {
1098       char buf[MSG_SIZ];
1099       int len;
1100
1101       /* Check for variants that are supported only in ICS mode,
1102          or not at all.  Some that are accepted here nevertheless
1103          have bugs; see comments below.
1104       */
1105       VariantClass variant = StringToVariant(appData.variant);
1106       switch (variant) {
1107       case VariantBughouse:     /* need four players and two boards */
1108       case VariantKriegspiel:   /* need to hide pieces and move details */
1109         /* case VariantFischeRandom: (Fabien: moved below) */
1110         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1111         if( (len >= MSG_SIZ) && appData.debugMode )
1112           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1113
1114         DisplayFatalError(buf, 0, 2);
1115         return;
1116
1117       case VariantUnknown:
1118       case VariantLoadable:
1119       case Variant29:
1120       case Variant30:
1121       case Variant31:
1122       case Variant32:
1123       case Variant33:
1124       case Variant34:
1125       case Variant35:
1126       case Variant36:
1127       default:
1128         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1129         if( (len >= MSG_SIZ) && appData.debugMode )
1130           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1131
1132         DisplayFatalError(buf, 0, 2);
1133         return;
1134
1135       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1136       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1137       case VariantGothic:     /* [HGM] should work */
1138       case VariantCapablanca: /* [HGM] should work */
1139       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1140       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1141       case VariantKnightmate: /* [HGM] should work */
1142       case VariantCylinder:   /* [HGM] untested */
1143       case VariantFalcon:     /* [HGM] untested */
1144       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1145                                  offboard interposition not understood */
1146       case VariantNormal:     /* definitely works! */
1147       case VariantWildCastle: /* pieces not automatically shuffled */
1148       case VariantNoCastle:   /* pieces not automatically shuffled */
1149       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1150       case VariantLosers:     /* should work except for win condition,
1151                                  and doesn't know captures are mandatory */
1152       case VariantSuicide:    /* should work except for win condition,
1153                                  and doesn't know captures are mandatory */
1154       case VariantGiveaway:   /* should work except for win condition,
1155                                  and doesn't know captures are mandatory */
1156       case VariantTwoKings:   /* should work */
1157       case VariantAtomic:     /* should work except for win condition */
1158       case Variant3Check:     /* should work except for win condition */
1159       case VariantShatranj:   /* should work except for all win conditions */
1160       case VariantMakruk:     /* should work except for draw countdown */
1161       case VariantBerolina:   /* might work if TestLegality is off */
1162       case VariantCapaRandom: /* should work */
1163       case VariantJanus:      /* should work */
1164       case VariantSuper:      /* experimental */
1165       case VariantGreat:      /* experimental, requires legality testing to be off */
1166       case VariantSChess:     /* S-Chess, should work */
1167       case VariantGrand:      /* should work */
1168       case VariantSpartan:    /* should work */
1169         break;
1170       }
1171     }
1172
1173 }
1174
1175 int
1176 NextIntegerFromString (char ** str, long * value)
1177 {
1178     int result = -1;
1179     char * s = *str;
1180
1181     while( *s == ' ' || *s == '\t' ) {
1182         s++;
1183     }
1184
1185     *value = 0;
1186
1187     if( *s >= '0' && *s <= '9' ) {
1188         while( *s >= '0' && *s <= '9' ) {
1189             *value = *value * 10 + (*s - '0');
1190             s++;
1191         }
1192
1193         result = 0;
1194     }
1195
1196     *str = s;
1197
1198     return result;
1199 }
1200
1201 int
1202 NextTimeControlFromString (char ** str, long * value)
1203 {
1204     long temp;
1205     int result = NextIntegerFromString( str, &temp );
1206
1207     if( result == 0 ) {
1208         *value = temp * 60; /* Minutes */
1209         if( **str == ':' ) {
1210             (*str)++;
1211             result = NextIntegerFromString( str, &temp );
1212             *value += temp; /* Seconds */
1213         }
1214     }
1215
1216     return result;
1217 }
1218
1219 int
1220 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1221 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1222     int result = -1, type = 0; long temp, temp2;
1223
1224     if(**str != ':') return -1; // old params remain in force!
1225     (*str)++;
1226     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1227     if( NextIntegerFromString( str, &temp ) ) return -1;
1228     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1229
1230     if(**str != '/') {
1231         /* time only: incremental or sudden-death time control */
1232         if(**str == '+') { /* increment follows; read it */
1233             (*str)++;
1234             if(**str == '!') type = *(*str)++; // Bronstein TC
1235             if(result = NextIntegerFromString( str, &temp2)) return -1;
1236             *inc = temp2 * 1000;
1237             if(**str == '.') { // read fraction of increment
1238                 char *start = ++(*str);
1239                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1240                 temp2 *= 1000;
1241                 while(start++ < *str) temp2 /= 10;
1242                 *inc += temp2;
1243             }
1244         } else *inc = 0;
1245         *moves = 0; *tc = temp * 1000; *incType = type;
1246         return 0;
1247     }
1248
1249     (*str)++; /* classical time control */
1250     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1251
1252     if(result == 0) {
1253         *moves = temp;
1254         *tc    = temp2 * 1000;
1255         *inc   = 0;
1256         *incType = type;
1257     }
1258     return result;
1259 }
1260
1261 int
1262 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1263 {   /* [HGM] get time to add from the multi-session time-control string */
1264     int incType, moves=1; /* kludge to force reading of first session */
1265     long time, increment;
1266     char *s = tcString;
1267
1268     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1269     do {
1270         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1271         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1272         if(movenr == -1) return time;    /* last move before new session     */
1273         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1274         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1275         if(!moves) return increment;     /* current session is incremental   */
1276         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1277     } while(movenr >= -1);               /* try again for next session       */
1278
1279     return 0; // no new time quota on this move
1280 }
1281
1282 int
1283 ParseTimeControl (char *tc, float ti, int mps)
1284 {
1285   long tc1;
1286   long tc2;
1287   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1288   int min, sec=0;
1289
1290   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1291   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1292       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1293   if(ti > 0) {
1294
1295     if(mps)
1296       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1297     else
1298       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1299   } else {
1300     if(mps)
1301       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1302     else
1303       snprintf(buf, MSG_SIZ, ":%s", mytc);
1304   }
1305   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1306
1307   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1308     return FALSE;
1309   }
1310
1311   if( *tc == '/' ) {
1312     /* Parse second time control */
1313     tc++;
1314
1315     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1316       return FALSE;
1317     }
1318
1319     if( tc2 == 0 ) {
1320       return FALSE;
1321     }
1322
1323     timeControl_2 = tc2 * 1000;
1324   }
1325   else {
1326     timeControl_2 = 0;
1327   }
1328
1329   if( tc1 == 0 ) {
1330     return FALSE;
1331   }
1332
1333   timeControl = tc1 * 1000;
1334
1335   if (ti >= 0) {
1336     timeIncrement = ti * 1000;  /* convert to ms */
1337     movesPerSession = 0;
1338   } else {
1339     timeIncrement = 0;
1340     movesPerSession = mps;
1341   }
1342   return TRUE;
1343 }
1344
1345 void
1346 InitBackEnd2 ()
1347 {
1348     if (appData.debugMode) {
1349 #    ifdef __GIT_VERSION
1350       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1351 #    else
1352       fprintf(debugFP, "Version: %s\n", programVersion);
1353 #    endif
1354     }
1355     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1356
1357     set_cont_sequence(appData.wrapContSeq);
1358     if (appData.matchGames > 0) {
1359         appData.matchMode = TRUE;
1360     } else if (appData.matchMode) {
1361         appData.matchGames = 1;
1362     }
1363     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1364         appData.matchGames = appData.sameColorGames;
1365     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1366         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1367         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1368     }
1369     Reset(TRUE, FALSE);
1370     if (appData.noChessProgram || first.protocolVersion == 1) {
1371       InitBackEnd3();
1372     } else {
1373       /* kludge: allow timeout for initial "feature" commands */
1374       FreezeUI();
1375       DisplayMessage("", _("Starting chess program"));
1376       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1377     }
1378 }
1379
1380 int
1381 CalculateIndex (int index, int gameNr)
1382 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1383     int res;
1384     if(index > 0) return index; // fixed nmber
1385     if(index == 0) return 1;
1386     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1387     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1388     return res;
1389 }
1390
1391 int
1392 LoadGameOrPosition (int gameNr)
1393 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1394     if (*appData.loadGameFile != NULLCHAR) {
1395         if (!LoadGameFromFile(appData.loadGameFile,
1396                 CalculateIndex(appData.loadGameIndex, gameNr),
1397                               appData.loadGameFile, FALSE)) {
1398             DisplayFatalError(_("Bad game file"), 0, 1);
1399             return 0;
1400         }
1401     } else if (*appData.loadPositionFile != NULLCHAR) {
1402         if (!LoadPositionFromFile(appData.loadPositionFile,
1403                 CalculateIndex(appData.loadPositionIndex, gameNr),
1404                                   appData.loadPositionFile)) {
1405             DisplayFatalError(_("Bad position file"), 0, 1);
1406             return 0;
1407         }
1408     }
1409     return 1;
1410 }
1411
1412 void
1413 ReserveGame (int gameNr, char resChar)
1414 {
1415     FILE *tf = fopen(appData.tourneyFile, "r+");
1416     char *p, *q, c, buf[MSG_SIZ];
1417     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1418     safeStrCpy(buf, lastMsg, MSG_SIZ);
1419     DisplayMessage(_("Pick new game"), "");
1420     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1421     ParseArgsFromFile(tf);
1422     p = q = appData.results;
1423     if(appData.debugMode) {
1424       char *r = appData.participants;
1425       fprintf(debugFP, "results = '%s'\n", p);
1426       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1427       fprintf(debugFP, "\n");
1428     }
1429     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1430     nextGame = q - p;
1431     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1432     safeStrCpy(q, p, strlen(p) + 2);
1433     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1434     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1435     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1436         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1437         q[nextGame] = '*';
1438     }
1439     fseek(tf, -(strlen(p)+4), SEEK_END);
1440     c = fgetc(tf);
1441     if(c != '"') // depending on DOS or Unix line endings we can be one off
1442          fseek(tf, -(strlen(p)+2), SEEK_END);
1443     else fseek(tf, -(strlen(p)+3), SEEK_END);
1444     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1445     DisplayMessage(buf, "");
1446     free(p); appData.results = q;
1447     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1448        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1449       int round = appData.defaultMatchGames * appData.tourneyType;
1450       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1451          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1452         UnloadEngine(&first);  // next game belongs to other pairing;
1453         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1454     }
1455     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1456 }
1457
1458 void
1459 MatchEvent (int mode)
1460 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1461         int dummy;
1462         if(matchMode) { // already in match mode: switch it off
1463             abortMatch = TRUE;
1464             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1465             return;
1466         }
1467 //      if(gameMode != BeginningOfGame) {
1468 //          DisplayError(_("You can only start a match from the initial position."), 0);
1469 //          return;
1470 //      }
1471         abortMatch = FALSE;
1472         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1473         /* Set up machine vs. machine match */
1474         nextGame = 0;
1475         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1476         if(appData.tourneyFile[0]) {
1477             ReserveGame(-1, 0);
1478             if(nextGame > appData.matchGames) {
1479                 char buf[MSG_SIZ];
1480                 if(strchr(appData.results, '*') == NULL) {
1481                     FILE *f;
1482                     appData.tourneyCycles++;
1483                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1484                         fclose(f);
1485                         NextTourneyGame(-1, &dummy);
1486                         ReserveGame(-1, 0);
1487                         if(nextGame <= appData.matchGames) {
1488                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1489                             matchMode = mode;
1490                             ScheduleDelayedEvent(NextMatchGame, 10000);
1491                             return;
1492                         }
1493                     }
1494                 }
1495                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1496                 DisplayError(buf, 0);
1497                 appData.tourneyFile[0] = 0;
1498                 return;
1499             }
1500         } else
1501         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1502             DisplayFatalError(_("Can't have a match with no chess programs"),
1503                               0, 2);
1504             return;
1505         }
1506         matchMode = mode;
1507         matchGame = roundNr = 1;
1508         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1509         NextMatchGame();
1510 }
1511
1512 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1513
1514 void
1515 InitBackEnd3 P((void))
1516 {
1517     GameMode initialMode;
1518     char buf[MSG_SIZ];
1519     int err, len;
1520
1521     InitChessProgram(&first, startedFromSetupPosition);
1522
1523     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1524         free(programVersion);
1525         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1526         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1527         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1528     }
1529
1530     if (appData.icsActive) {
1531 #ifdef WIN32
1532         /* [DM] Make a console window if needed [HGM] merged ifs */
1533         ConsoleCreate();
1534 #endif
1535         err = establish();
1536         if (err != 0)
1537           {
1538             if (*appData.icsCommPort != NULLCHAR)
1539               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1540                              appData.icsCommPort);
1541             else
1542               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1543                         appData.icsHost, appData.icsPort);
1544
1545             if( (len >= MSG_SIZ) && appData.debugMode )
1546               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1547
1548             DisplayFatalError(buf, err, 1);
1549             return;
1550         }
1551         SetICSMode();
1552         telnetISR =
1553           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1554         fromUserISR =
1555           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1556         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1557             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1558     } else if (appData.noChessProgram) {
1559         SetNCPMode();
1560     } else {
1561         SetGNUMode();
1562     }
1563
1564     if (*appData.cmailGameName != NULLCHAR) {
1565         SetCmailMode();
1566         OpenLoopback(&cmailPR);
1567         cmailISR =
1568           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1569     }
1570
1571     ThawUI();
1572     DisplayMessage("", "");
1573     if (StrCaseCmp(appData.initialMode, "") == 0) {
1574       initialMode = BeginningOfGame;
1575       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1576         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1577         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1578         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1579         ModeHighlight();
1580       }
1581     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1582       initialMode = TwoMachinesPlay;
1583     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1584       initialMode = AnalyzeFile;
1585     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1586       initialMode = AnalyzeMode;
1587     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1588       initialMode = MachinePlaysWhite;
1589     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1590       initialMode = MachinePlaysBlack;
1591     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1592       initialMode = EditGame;
1593     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1594       initialMode = EditPosition;
1595     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1596       initialMode = Training;
1597     } else {
1598       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1599       if( (len >= MSG_SIZ) && appData.debugMode )
1600         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1601
1602       DisplayFatalError(buf, 0, 2);
1603       return;
1604     }
1605
1606     if (appData.matchMode) {
1607         if(appData.tourneyFile[0]) { // start tourney from command line
1608             FILE *f;
1609             if(f = fopen(appData.tourneyFile, "r")) {
1610                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1611                 fclose(f);
1612                 appData.clockMode = TRUE;
1613                 SetGNUMode();
1614             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1615         }
1616         MatchEvent(TRUE);
1617     } else if (*appData.cmailGameName != NULLCHAR) {
1618         /* Set up cmail mode */
1619         ReloadCmailMsgEvent(TRUE);
1620     } else {
1621         /* Set up other modes */
1622         if (initialMode == AnalyzeFile) {
1623           if (*appData.loadGameFile == NULLCHAR) {
1624             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1625             return;
1626           }
1627         }
1628         if (*appData.loadGameFile != NULLCHAR) {
1629             (void) LoadGameFromFile(appData.loadGameFile,
1630                                     appData.loadGameIndex,
1631                                     appData.loadGameFile, TRUE);
1632         } else if (*appData.loadPositionFile != NULLCHAR) {
1633             (void) LoadPositionFromFile(appData.loadPositionFile,
1634                                         appData.loadPositionIndex,
1635                                         appData.loadPositionFile);
1636             /* [HGM] try to make self-starting even after FEN load */
1637             /* to allow automatic setup of fairy variants with wtm */
1638             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1639                 gameMode = BeginningOfGame;
1640                 setboardSpoiledMachineBlack = 1;
1641             }
1642             /* [HGM] loadPos: make that every new game uses the setup */
1643             /* from file as long as we do not switch variant          */
1644             if(!blackPlaysFirst) {
1645                 startedFromPositionFile = TRUE;
1646                 CopyBoard(filePosition, boards[0]);
1647             }
1648         }
1649         if (initialMode == AnalyzeMode) {
1650           if (appData.noChessProgram) {
1651             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1656             return;
1657           }
1658           AnalyzeModeEvent();
1659         } else if (initialMode == AnalyzeFile) {
1660           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1661           ShowThinkingEvent();
1662           AnalyzeFileEvent();
1663           AnalysisPeriodicEvent(1);
1664         } else if (initialMode == MachinePlaysWhite) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           MachineWhiteEvent();
1676         } else if (initialMode == MachinePlaysBlack) {
1677           if (appData.noChessProgram) {
1678             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1679                               0, 2);
1680             return;
1681           }
1682           if (appData.icsActive) {
1683             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1684                               0, 2);
1685             return;
1686           }
1687           MachineBlackEvent();
1688         } else if (initialMode == TwoMachinesPlay) {
1689           if (appData.noChessProgram) {
1690             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1691                               0, 2);
1692             return;
1693           }
1694           if (appData.icsActive) {
1695             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1696                               0, 2);
1697             return;
1698           }
1699           TwoMachinesEvent();
1700         } else if (initialMode == EditGame) {
1701           EditGameEvent();
1702         } else if (initialMode == EditPosition) {
1703           EditPositionEvent();
1704         } else if (initialMode == Training) {
1705           if (*appData.loadGameFile == NULLCHAR) {
1706             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1707             return;
1708           }
1709           TrainingEvent();
1710         }
1711     }
1712 }
1713
1714 void
1715 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1716 {
1717     DisplayBook(current+1);
1718
1719     MoveHistorySet( movelist, first, last, current, pvInfoList );
1720
1721     EvalGraphSet( first, last, current, pvInfoList );
1722
1723     MakeEngineOutputTitle();
1724 }
1725
1726 /*
1727  * Establish will establish a contact to a remote host.port.
1728  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1729  *  used to talk to the host.
1730  * Returns 0 if okay, error code if not.
1731  */
1732 int
1733 establish ()
1734 {
1735     char buf[MSG_SIZ];
1736
1737     if (*appData.icsCommPort != NULLCHAR) {
1738         /* Talk to the host through a serial comm port */
1739         return OpenCommPort(appData.icsCommPort, &icsPR);
1740
1741     } else if (*appData.gateway != NULLCHAR) {
1742         if (*appData.remoteShell == NULLCHAR) {
1743             /* Use the rcmd protocol to run telnet program on a gateway host */
1744             snprintf(buf, sizeof(buf), "%s %s %s",
1745                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1746             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1747
1748         } else {
1749             /* Use the rsh program to run telnet program on a gateway host */
1750             if (*appData.remoteUser == NULLCHAR) {
1751                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1752                         appData.gateway, appData.telnetProgram,
1753                         appData.icsHost, appData.icsPort);
1754             } else {
1755                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1756                         appData.remoteShell, appData.gateway,
1757                         appData.remoteUser, appData.telnetProgram,
1758                         appData.icsHost, appData.icsPort);
1759             }
1760             return StartChildProcess(buf, "", &icsPR);
1761
1762         }
1763     } else if (appData.useTelnet) {
1764         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1765
1766     } else {
1767         /* TCP socket interface differs somewhat between
1768            Unix and NT; handle details in the front end.
1769            */
1770         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1771     }
1772 }
1773
1774 void
1775 EscapeExpand (char *p, char *q)
1776 {       // [HGM] initstring: routine to shape up string arguments
1777         while(*p++ = *q++) if(p[-1] == '\\')
1778             switch(*q++) {
1779                 case 'n': p[-1] = '\n'; break;
1780                 case 'r': p[-1] = '\r'; break;
1781                 case 't': p[-1] = '\t'; break;
1782                 case '\\': p[-1] = '\\'; break;
1783                 case 0: *p = 0; return;
1784                 default: p[-1] = q[-1]; break;
1785             }
1786 }
1787
1788 void
1789 show_bytes (FILE *fp, char *buf, int count)
1790 {
1791     while (count--) {
1792         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1793             fprintf(fp, "\\%03o", *buf & 0xff);
1794         } else {
1795             putc(*buf, fp);
1796         }
1797         buf++;
1798     }
1799     fflush(fp);
1800 }
1801
1802 /* Returns an errno value */
1803 int
1804 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1805 {
1806     char buf[8192], *p, *q, *buflim;
1807     int left, newcount, outcount;
1808
1809     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1810         *appData.gateway != NULLCHAR) {
1811         if (appData.debugMode) {
1812             fprintf(debugFP, ">ICS: ");
1813             show_bytes(debugFP, message, count);
1814             fprintf(debugFP, "\n");
1815         }
1816         return OutputToProcess(pr, message, count, outError);
1817     }
1818
1819     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1820     p = message;
1821     q = buf;
1822     left = count;
1823     newcount = 0;
1824     while (left) {
1825         if (q >= buflim) {
1826             if (appData.debugMode) {
1827                 fprintf(debugFP, ">ICS: ");
1828                 show_bytes(debugFP, buf, newcount);
1829                 fprintf(debugFP, "\n");
1830             }
1831             outcount = OutputToProcess(pr, buf, newcount, outError);
1832             if (outcount < newcount) return -1; /* to be sure */
1833             q = buf;
1834             newcount = 0;
1835         }
1836         if (*p == '\n') {
1837             *q++ = '\r';
1838             newcount++;
1839         } else if (((unsigned char) *p) == TN_IAC) {
1840             *q++ = (char) TN_IAC;
1841             newcount ++;
1842         }
1843         *q++ = *p++;
1844         newcount++;
1845         left--;
1846     }
1847     if (appData.debugMode) {
1848         fprintf(debugFP, ">ICS: ");
1849         show_bytes(debugFP, buf, newcount);
1850         fprintf(debugFP, "\n");
1851     }
1852     outcount = OutputToProcess(pr, buf, newcount, outError);
1853     if (outcount < newcount) return -1; /* to be sure */
1854     return count;
1855 }
1856
1857 void
1858 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1859 {
1860     int outError, outCount;
1861     static int gotEof = 0;
1862     static FILE *ini;
1863
1864     /* Pass data read from player on to ICS */
1865     if (count > 0) {
1866         gotEof = 0;
1867         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1868         if (outCount < count) {
1869             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1870         }
1871         if(have_sent_ICS_logon == 2) {
1872           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1873             fprintf(ini, "%s", message);
1874             have_sent_ICS_logon = 3;
1875           } else
1876             have_sent_ICS_logon = 1;
1877         } else if(have_sent_ICS_logon == 3) {
1878             fprintf(ini, "%s", message);
1879             fclose(ini);
1880           have_sent_ICS_logon = 1;
1881         }
1882     } else if (count < 0) {
1883         RemoveInputSource(isr);
1884         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1885     } else if (gotEof++ > 0) {
1886         RemoveInputSource(isr);
1887         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1888     }
1889 }
1890
1891 void
1892 KeepAlive ()
1893 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1894     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1895     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1896     SendToICS("date\n");
1897     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1898 }
1899
1900 /* added routine for printf style output to ics */
1901 void
1902 ics_printf (char *format, ...)
1903 {
1904     char buffer[MSG_SIZ];
1905     va_list args;
1906
1907     va_start(args, format);
1908     vsnprintf(buffer, sizeof(buffer), format, args);
1909     buffer[sizeof(buffer)-1] = '\0';
1910     SendToICS(buffer);
1911     va_end(args);
1912 }
1913
1914 void
1915 SendToICS (char *s)
1916 {
1917     int count, outCount, outError;
1918
1919     if (icsPR == NoProc) return;
1920
1921     count = strlen(s);
1922     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1923     if (outCount < count) {
1924         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1925     }
1926 }
1927
1928 /* This is used for sending logon scripts to the ICS. Sending
1929    without a delay causes problems when using timestamp on ICC
1930    (at least on my machine). */
1931 void
1932 SendToICSDelayed (char *s, long msdelay)
1933 {
1934     int count, outCount, outError;
1935
1936     if (icsPR == NoProc) return;
1937
1938     count = strlen(s);
1939     if (appData.debugMode) {
1940         fprintf(debugFP, ">ICS: ");
1941         show_bytes(debugFP, s, count);
1942         fprintf(debugFP, "\n");
1943     }
1944     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1945                                       msdelay);
1946     if (outCount < count) {
1947         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1948     }
1949 }
1950
1951
1952 /* Remove all highlighting escape sequences in s
1953    Also deletes any suffix starting with '('
1954    */
1955 char *
1956 StripHighlightAndTitle (char *s)
1957 {
1958     static char retbuf[MSG_SIZ];
1959     char *p = retbuf;
1960
1961     while (*s != NULLCHAR) {
1962         while (*s == '\033') {
1963             while (*s != NULLCHAR && !isalpha(*s)) s++;
1964             if (*s != NULLCHAR) s++;
1965         }
1966         while (*s != NULLCHAR && *s != '\033') {
1967             if (*s == '(' || *s == '[') {
1968                 *p = NULLCHAR;
1969                 return retbuf;
1970             }
1971             *p++ = *s++;
1972         }
1973     }
1974     *p = NULLCHAR;
1975     return retbuf;
1976 }
1977
1978 /* Remove all highlighting escape sequences in s */
1979 char *
1980 StripHighlight (char *s)
1981 {
1982     static char retbuf[MSG_SIZ];
1983     char *p = retbuf;
1984
1985     while (*s != NULLCHAR) {
1986         while (*s == '\033') {
1987             while (*s != NULLCHAR && !isalpha(*s)) s++;
1988             if (*s != NULLCHAR) s++;
1989         }
1990         while (*s != NULLCHAR && *s != '\033') {
1991             *p++ = *s++;
1992         }
1993     }
1994     *p = NULLCHAR;
1995     return retbuf;
1996 }
1997
1998 char *variantNames[] = VARIANT_NAMES;
1999 char *
2000 VariantName (VariantClass v)
2001 {
2002     return variantNames[v];
2003 }
2004
2005
2006 /* Identify a variant from the strings the chess servers use or the
2007    PGN Variant tag names we use. */
2008 VariantClass
2009 StringToVariant (char *e)
2010 {
2011     char *p;
2012     int wnum = -1;
2013     VariantClass v = VariantNormal;
2014     int i, found = FALSE;
2015     char buf[MSG_SIZ];
2016     int len;
2017
2018     if (!e) return v;
2019
2020     /* [HGM] skip over optional board-size prefixes */
2021     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2022         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2023         while( *e++ != '_');
2024     }
2025
2026     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2027         v = VariantNormal;
2028         found = TRUE;
2029     } else
2030     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2031       if (StrCaseStr(e, variantNames[i])) {
2032         v = (VariantClass) i;
2033         found = TRUE;
2034         break;
2035       }
2036     }
2037
2038     if (!found) {
2039       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2040           || StrCaseStr(e, "wild/fr")
2041           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2042         v = VariantFischeRandom;
2043       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2044                  (i = 1, p = StrCaseStr(e, "w"))) {
2045         p += i;
2046         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2047         if (isdigit(*p)) {
2048           wnum = atoi(p);
2049         } else {
2050           wnum = -1;
2051         }
2052         switch (wnum) {
2053         case 0: /* FICS only, actually */
2054         case 1:
2055           /* Castling legal even if K starts on d-file */
2056           v = VariantWildCastle;
2057           break;
2058         case 2:
2059         case 3:
2060         case 4:
2061           /* Castling illegal even if K & R happen to start in
2062              normal positions. */
2063           v = VariantNoCastle;
2064           break;
2065         case 5:
2066         case 7:
2067         case 8:
2068         case 10:
2069         case 11:
2070         case 12:
2071         case 13:
2072         case 14:
2073         case 15:
2074         case 18:
2075         case 19:
2076           /* Castling legal iff K & R start in normal positions */
2077           v = VariantNormal;
2078           break;
2079         case 6:
2080         case 20:
2081         case 21:
2082           /* Special wilds for position setup; unclear what to do here */
2083           v = VariantLoadable;
2084           break;
2085         case 9:
2086           /* Bizarre ICC game */
2087           v = VariantTwoKings;
2088           break;
2089         case 16:
2090           v = VariantKriegspiel;
2091           break;
2092         case 17:
2093           v = VariantLosers;
2094           break;
2095         case 22:
2096           v = VariantFischeRandom;
2097           break;
2098         case 23:
2099           v = VariantCrazyhouse;
2100           break;
2101         case 24:
2102           v = VariantBughouse;
2103           break;
2104         case 25:
2105           v = Variant3Check;
2106           break;
2107         case 26:
2108           /* Not quite the same as FICS suicide! */
2109           v = VariantGiveaway;
2110           break;
2111         case 27:
2112           v = VariantAtomic;
2113           break;
2114         case 28:
2115           v = VariantShatranj;
2116           break;
2117
2118         /* Temporary names for future ICC types.  The name *will* change in
2119            the next xboard/WinBoard release after ICC defines it. */
2120         case 29:
2121           v = Variant29;
2122           break;
2123         case 30:
2124           v = Variant30;
2125           break;
2126         case 31:
2127           v = Variant31;
2128           break;
2129         case 32:
2130           v = Variant32;
2131           break;
2132         case 33:
2133           v = Variant33;
2134           break;
2135         case 34:
2136           v = Variant34;
2137           break;
2138         case 35:
2139           v = Variant35;
2140           break;
2141         case 36:
2142           v = Variant36;
2143           break;
2144         case 37:
2145           v = VariantShogi;
2146           break;
2147         case 38:
2148           v = VariantXiangqi;
2149           break;
2150         case 39:
2151           v = VariantCourier;
2152           break;
2153         case 40:
2154           v = VariantGothic;
2155           break;
2156         case 41:
2157           v = VariantCapablanca;
2158           break;
2159         case 42:
2160           v = VariantKnightmate;
2161           break;
2162         case 43:
2163           v = VariantFairy;
2164           break;
2165         case 44:
2166           v = VariantCylinder;
2167           break;
2168         case 45:
2169           v = VariantFalcon;
2170           break;
2171         case 46:
2172           v = VariantCapaRandom;
2173           break;
2174         case 47:
2175           v = VariantBerolina;
2176           break;
2177         case 48:
2178           v = VariantJanus;
2179           break;
2180         case 49:
2181           v = VariantSuper;
2182           break;
2183         case 50:
2184           v = VariantGreat;
2185           break;
2186         case -1:
2187           /* Found "wild" or "w" in the string but no number;
2188              must assume it's normal chess. */
2189           v = VariantNormal;
2190           break;
2191         default:
2192           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2193           if( (len >= MSG_SIZ) && appData.debugMode )
2194             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2195
2196           DisplayError(buf, 0);
2197           v = VariantUnknown;
2198           break;
2199         }
2200       }
2201     }
2202     if (appData.debugMode) {
2203       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2204               e, wnum, VariantName(v));
2205     }
2206     return v;
2207 }
2208
2209 static int leftover_start = 0, leftover_len = 0;
2210 char star_match[STAR_MATCH_N][MSG_SIZ];
2211
2212 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2213    advance *index beyond it, and set leftover_start to the new value of
2214    *index; else return FALSE.  If pattern contains the character '*', it
2215    matches any sequence of characters not containing '\r', '\n', or the
2216    character following the '*' (if any), and the matched sequence(s) are
2217    copied into star_match.
2218    */
2219 int
2220 looking_at ( char *buf, int *index, char *pattern)
2221 {
2222     char *bufp = &buf[*index], *patternp = pattern;
2223     int star_count = 0;
2224     char *matchp = star_match[0];
2225
2226     for (;;) {
2227         if (*patternp == NULLCHAR) {
2228             *index = leftover_start = bufp - buf;
2229             *matchp = NULLCHAR;
2230             return TRUE;
2231         }
2232         if (*bufp == NULLCHAR) return FALSE;
2233         if (*patternp == '*') {
2234             if (*bufp == *(patternp + 1)) {
2235                 *matchp = NULLCHAR;
2236                 matchp = star_match[++star_count];
2237                 patternp += 2;
2238                 bufp++;
2239                 continue;
2240             } else if (*bufp == '\n' || *bufp == '\r') {
2241                 patternp++;
2242                 if (*patternp == NULLCHAR)
2243                   continue;
2244                 else
2245                   return FALSE;
2246             } else {
2247                 *matchp++ = *bufp++;
2248                 continue;
2249             }
2250         }
2251         if (*patternp != *bufp) return FALSE;
2252         patternp++;
2253         bufp++;
2254     }
2255 }
2256
2257 void
2258 SendToPlayer (char *data, int length)
2259 {
2260     int error, outCount;
2261     outCount = OutputToProcess(NoProc, data, length, &error);
2262     if (outCount < length) {
2263         DisplayFatalError(_("Error writing to display"), error, 1);
2264     }
2265 }
2266
2267 void
2268 PackHolding (char packed[], char *holding)
2269 {
2270     char *p = holding;
2271     char *q = packed;
2272     int runlength = 0;
2273     int curr = 9999;
2274     do {
2275         if (*p == curr) {
2276             runlength++;
2277         } else {
2278             switch (runlength) {
2279               case 0:
2280                 break;
2281               case 1:
2282                 *q++ = curr;
2283                 break;
2284               case 2:
2285                 *q++ = curr;
2286                 *q++ = curr;
2287                 break;
2288               default:
2289                 sprintf(q, "%d", runlength);
2290                 while (*q) q++;
2291                 *q++ = curr;
2292                 break;
2293             }
2294             runlength = 1;
2295             curr = *p;
2296         }
2297     } while (*p++);
2298     *q = NULLCHAR;
2299 }
2300
2301 /* Telnet protocol requests from the front end */
2302 void
2303 TelnetRequest (unsigned char ddww, unsigned char option)
2304 {
2305     unsigned char msg[3];
2306     int outCount, outError;
2307
2308     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2309
2310     if (appData.debugMode) {
2311         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2312         switch (ddww) {
2313           case TN_DO:
2314             ddwwStr = "DO";
2315             break;
2316           case TN_DONT:
2317             ddwwStr = "DONT";
2318             break;
2319           case TN_WILL:
2320             ddwwStr = "WILL";
2321             break;
2322           case TN_WONT:
2323             ddwwStr = "WONT";
2324             break;
2325           default:
2326             ddwwStr = buf1;
2327             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2328             break;
2329         }
2330         switch (option) {
2331           case TN_ECHO:
2332             optionStr = "ECHO";
2333             break;
2334           default:
2335             optionStr = buf2;
2336             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2337             break;
2338         }
2339         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2340     }
2341     msg[0] = TN_IAC;
2342     msg[1] = ddww;
2343     msg[2] = option;
2344     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2345     if (outCount < 3) {
2346         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2347     }
2348 }
2349
2350 void
2351 DoEcho ()
2352 {
2353     if (!appData.icsActive) return;
2354     TelnetRequest(TN_DO, TN_ECHO);
2355 }
2356
2357 void
2358 DontEcho ()
2359 {
2360     if (!appData.icsActive) return;
2361     TelnetRequest(TN_DONT, TN_ECHO);
2362 }
2363
2364 void
2365 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2366 {
2367     /* put the holdings sent to us by the server on the board holdings area */
2368     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2369     char p;
2370     ChessSquare piece;
2371
2372     if(gameInfo.holdingsWidth < 2)  return;
2373     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2374         return; // prevent overwriting by pre-board holdings
2375
2376     if( (int)lowestPiece >= BlackPawn ) {
2377         holdingsColumn = 0;
2378         countsColumn = 1;
2379         holdingsStartRow = BOARD_HEIGHT-1;
2380         direction = -1;
2381     } else {
2382         holdingsColumn = BOARD_WIDTH-1;
2383         countsColumn = BOARD_WIDTH-2;
2384         holdingsStartRow = 0;
2385         direction = 1;
2386     }
2387
2388     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2389         board[i][holdingsColumn] = EmptySquare;
2390         board[i][countsColumn]   = (ChessSquare) 0;
2391     }
2392     while( (p=*holdings++) != NULLCHAR ) {
2393         piece = CharToPiece( ToUpper(p) );
2394         if(piece == EmptySquare) continue;
2395         /*j = (int) piece - (int) WhitePawn;*/
2396         j = PieceToNumber(piece);
2397         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2398         if(j < 0) continue;               /* should not happen */
2399         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2400         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2401         board[holdingsStartRow+j*direction][countsColumn]++;
2402     }
2403 }
2404
2405
2406 void
2407 VariantSwitch (Board board, VariantClass newVariant)
2408 {
2409    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2410    static Board oldBoard;
2411
2412    startedFromPositionFile = FALSE;
2413    if(gameInfo.variant == newVariant) return;
2414
2415    /* [HGM] This routine is called each time an assignment is made to
2416     * gameInfo.variant during a game, to make sure the board sizes
2417     * are set to match the new variant. If that means adding or deleting
2418     * holdings, we shift the playing board accordingly
2419     * This kludge is needed because in ICS observe mode, we get boards
2420     * of an ongoing game without knowing the variant, and learn about the
2421     * latter only later. This can be because of the move list we requested,
2422     * in which case the game history is refilled from the beginning anyway,
2423     * but also when receiving holdings of a crazyhouse game. In the latter
2424     * case we want to add those holdings to the already received position.
2425     */
2426
2427
2428    if (appData.debugMode) {
2429      fprintf(debugFP, "Switch board from %s to %s\n",
2430              VariantName(gameInfo.variant), VariantName(newVariant));
2431      setbuf(debugFP, NULL);
2432    }
2433    shuffleOpenings = 0;       /* [HGM] shuffle */
2434    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2435    switch(newVariant)
2436      {
2437      case VariantShogi:
2438        newWidth = 9;  newHeight = 9;
2439        gameInfo.holdingsSize = 7;
2440      case VariantBughouse:
2441      case VariantCrazyhouse:
2442        newHoldingsWidth = 2; break;
2443      case VariantGreat:
2444        newWidth = 10;
2445      case VariantSuper:
2446        newHoldingsWidth = 2;
2447        gameInfo.holdingsSize = 8;
2448        break;
2449      case VariantGothic:
2450      case VariantCapablanca:
2451      case VariantCapaRandom:
2452        newWidth = 10;
2453      default:
2454        newHoldingsWidth = gameInfo.holdingsSize = 0;
2455      };
2456
2457    if(newWidth  != gameInfo.boardWidth  ||
2458       newHeight != gameInfo.boardHeight ||
2459       newHoldingsWidth != gameInfo.holdingsWidth ) {
2460
2461      /* shift position to new playing area, if needed */
2462      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2463        for(i=0; i<BOARD_HEIGHT; i++)
2464          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2465            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2466              board[i][j];
2467        for(i=0; i<newHeight; i++) {
2468          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2469          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2470        }
2471      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2472        for(i=0; i<BOARD_HEIGHT; i++)
2473          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2474            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2475              board[i][j];
2476      }
2477      board[HOLDINGS_SET] = 0;
2478      gameInfo.boardWidth  = newWidth;
2479      gameInfo.boardHeight = newHeight;
2480      gameInfo.holdingsWidth = newHoldingsWidth;
2481      gameInfo.variant = newVariant;
2482      InitDrawingSizes(-2, 0);
2483    } else gameInfo.variant = newVariant;
2484    CopyBoard(oldBoard, board);   // remember correctly formatted board
2485      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2486    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2487 }
2488
2489 static int loggedOn = FALSE;
2490
2491 /*-- Game start info cache: --*/
2492 int gs_gamenum;
2493 char gs_kind[MSG_SIZ];
2494 static char player1Name[128] = "";
2495 static char player2Name[128] = "";
2496 static char cont_seq[] = "\n\\   ";
2497 static int player1Rating = -1;
2498 static int player2Rating = -1;
2499 /*----------------------------*/
2500
2501 ColorClass curColor = ColorNormal;
2502 int suppressKibitz = 0;
2503
2504 // [HGM] seekgraph
2505 Boolean soughtPending = FALSE;
2506 Boolean seekGraphUp;
2507 #define MAX_SEEK_ADS 200
2508 #define SQUARE 0x80
2509 char *seekAdList[MAX_SEEK_ADS];
2510 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2511 float tcList[MAX_SEEK_ADS];
2512 char colorList[MAX_SEEK_ADS];
2513 int nrOfSeekAds = 0;
2514 int minRating = 1010, maxRating = 2800;
2515 int hMargin = 10, vMargin = 20, h, w;
2516 extern int squareSize, lineGap;
2517
2518 void
2519 PlotSeekAd (int i)
2520 {
2521         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2522         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2523         if(r < minRating+100 && r >=0 ) r = minRating+100;
2524         if(r > maxRating) r = maxRating;
2525         if(tc < 1.f) tc = 1.f;
2526         if(tc > 95.f) tc = 95.f;
2527         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2528         y = ((double)r - minRating)/(maxRating - minRating)
2529             * (h-vMargin-squareSize/8-1) + vMargin;
2530         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2531         if(strstr(seekAdList[i], " u ")) color = 1;
2532         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2533            !strstr(seekAdList[i], "bullet") &&
2534            !strstr(seekAdList[i], "blitz") &&
2535            !strstr(seekAdList[i], "standard") ) color = 2;
2536         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2537         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2538 }
2539
2540 void
2541 PlotSingleSeekAd (int i)
2542 {
2543         PlotSeekAd(i);
2544 }
2545
2546 void
2547 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2548 {
2549         char buf[MSG_SIZ], *ext = "";
2550         VariantClass v = StringToVariant(type);
2551         if(strstr(type, "wild")) {
2552             ext = type + 4; // append wild number
2553             if(v == VariantFischeRandom) type = "chess960"; else
2554             if(v == VariantLoadable) type = "setup"; else
2555             type = VariantName(v);
2556         }
2557         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2558         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2559             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2560             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2561             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2562             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2563             seekNrList[nrOfSeekAds] = nr;
2564             zList[nrOfSeekAds] = 0;
2565             seekAdList[nrOfSeekAds++] = StrSave(buf);
2566             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2567         }
2568 }
2569
2570 void
2571 EraseSeekDot (int i)
2572 {
2573     int x = xList[i], y = yList[i], d=squareSize/4, k;
2574     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2575     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2576     // now replot every dot that overlapped
2577     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2578         int xx = xList[k], yy = yList[k];
2579         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2580             DrawSeekDot(xx, yy, colorList[k]);
2581     }
2582 }
2583
2584 void
2585 RemoveSeekAd (int nr)
2586 {
2587         int i;
2588         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2589             EraseSeekDot(i);
2590             if(seekAdList[i]) free(seekAdList[i]);
2591             seekAdList[i] = seekAdList[--nrOfSeekAds];
2592             seekNrList[i] = seekNrList[nrOfSeekAds];
2593             ratingList[i] = ratingList[nrOfSeekAds];
2594             colorList[i]  = colorList[nrOfSeekAds];
2595             tcList[i] = tcList[nrOfSeekAds];
2596             xList[i]  = xList[nrOfSeekAds];
2597             yList[i]  = yList[nrOfSeekAds];
2598             zList[i]  = zList[nrOfSeekAds];
2599             seekAdList[nrOfSeekAds] = NULL;
2600             break;
2601         }
2602 }
2603
2604 Boolean
2605 MatchSoughtLine (char *line)
2606 {
2607     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2608     int nr, base, inc, u=0; char dummy;
2609
2610     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2611        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2612        (u=1) &&
2613        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2614         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2615         // match: compact and save the line
2616         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2617         return TRUE;
2618     }
2619     return FALSE;
2620 }
2621
2622 int
2623 DrawSeekGraph ()
2624 {
2625     int i;
2626     if(!seekGraphUp) return FALSE;
2627     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2628     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2629
2630     DrawSeekBackground(0, 0, w, h);
2631     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2632     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2633     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2634         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2635         yy = h-1-yy;
2636         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2637         if(i%500 == 0) {
2638             char buf[MSG_SIZ];
2639             snprintf(buf, MSG_SIZ, "%d", i);
2640             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2641         }
2642     }
2643     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2644     for(i=1; i<100; i+=(i<10?1:5)) {
2645         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2646         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2647         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2648             char buf[MSG_SIZ];
2649             snprintf(buf, MSG_SIZ, "%d", i);
2650             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2651         }
2652     }
2653     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2654     return TRUE;
2655 }
2656
2657 int
2658 SeekGraphClick (ClickType click, int x, int y, int moving)
2659 {
2660     static int lastDown = 0, displayed = 0, lastSecond;
2661     if(y < 0) return FALSE;
2662     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2663         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2664         if(!seekGraphUp) return FALSE;
2665         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2666         DrawPosition(TRUE, NULL);
2667         return TRUE;
2668     }
2669     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2670         if(click == Release || moving) return FALSE;
2671         nrOfSeekAds = 0;
2672         soughtPending = TRUE;
2673         SendToICS(ics_prefix);
2674         SendToICS("sought\n"); // should this be "sought all"?
2675     } else { // issue challenge based on clicked ad
2676         int dist = 10000; int i, closest = 0, second = 0;
2677         for(i=0; i<nrOfSeekAds; i++) {
2678             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2679             if(d < dist) { dist = d; closest = i; }
2680             second += (d - zList[i] < 120); // count in-range ads
2681             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2682         }
2683         if(dist < 120) {
2684             char buf[MSG_SIZ];
2685             second = (second > 1);
2686             if(displayed != closest || second != lastSecond) {
2687                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2688                 lastSecond = second; displayed = closest;
2689             }
2690             if(click == Press) {
2691                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2692                 lastDown = closest;
2693                 return TRUE;
2694             } // on press 'hit', only show info
2695             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2696             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2697             SendToICS(ics_prefix);
2698             SendToICS(buf);
2699             return TRUE; // let incoming board of started game pop down the graph
2700         } else if(click == Release) { // release 'miss' is ignored
2701             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2702             if(moving == 2) { // right up-click
2703                 nrOfSeekAds = 0; // refresh graph
2704                 soughtPending = TRUE;
2705                 SendToICS(ics_prefix);
2706                 SendToICS("sought\n"); // should this be "sought all"?
2707             }
2708             return TRUE;
2709         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2710         // press miss or release hit 'pop down' seek graph
2711         seekGraphUp = FALSE;
2712         DrawPosition(TRUE, NULL);
2713     }
2714     return TRUE;
2715 }
2716
2717 void
2718 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2719 {
2720 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2721 #define STARTED_NONE 0
2722 #define STARTED_MOVES 1
2723 #define STARTED_BOARD 2
2724 #define STARTED_OBSERVE 3
2725 #define STARTED_HOLDINGS 4
2726 #define STARTED_CHATTER 5
2727 #define STARTED_COMMENT 6
2728 #define STARTED_MOVES_NOHIDE 7
2729
2730     static int started = STARTED_NONE;
2731     static char parse[20000];
2732     static int parse_pos = 0;
2733     static char buf[BUF_SIZE + 1];
2734     static int firstTime = TRUE, intfSet = FALSE;
2735     static ColorClass prevColor = ColorNormal;
2736     static int savingComment = FALSE;
2737     static int cmatch = 0; // continuation sequence match
2738     char *bp;
2739     char str[MSG_SIZ];
2740     int i, oldi;
2741     int buf_len;
2742     int next_out;
2743     int tkind;
2744     int backup;    /* [DM] For zippy color lines */
2745     char *p;
2746     char talker[MSG_SIZ]; // [HGM] chat
2747     int channel;
2748
2749     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2750
2751     if (appData.debugMode) {
2752       if (!error) {
2753         fprintf(debugFP, "<ICS: ");
2754         show_bytes(debugFP, data, count);
2755         fprintf(debugFP, "\n");
2756       }
2757     }
2758
2759     if (appData.debugMode) { int f = forwardMostMove;
2760         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2761                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2762                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2763     }
2764     if (count > 0) {
2765         /* If last read ended with a partial line that we couldn't parse,
2766            prepend it to the new read and try again. */
2767         if (leftover_len > 0) {
2768             for (i=0; i<leftover_len; i++)
2769               buf[i] = buf[leftover_start + i];
2770         }
2771
2772     /* copy new characters into the buffer */
2773     bp = buf + leftover_len;
2774     buf_len=leftover_len;
2775     for (i=0; i<count; i++)
2776     {
2777         // ignore these
2778         if (data[i] == '\r')
2779             continue;
2780
2781         // join lines split by ICS?
2782         if (!appData.noJoin)
2783         {
2784             /*
2785                 Joining just consists of finding matches against the
2786                 continuation sequence, and discarding that sequence
2787                 if found instead of copying it.  So, until a match
2788                 fails, there's nothing to do since it might be the
2789                 complete sequence, and thus, something we don't want
2790                 copied.
2791             */
2792             if (data[i] == cont_seq[cmatch])
2793             {
2794                 cmatch++;
2795                 if (cmatch == strlen(cont_seq))
2796                 {
2797                     cmatch = 0; // complete match.  just reset the counter
2798
2799                     /*
2800                         it's possible for the ICS to not include the space
2801                         at the end of the last word, making our [correct]
2802                         join operation fuse two separate words.  the server
2803                         does this when the space occurs at the width setting.
2804                     */
2805                     if (!buf_len || buf[buf_len-1] != ' ')
2806                     {
2807                         *bp++ = ' ';
2808                         buf_len++;
2809                     }
2810                 }
2811                 continue;
2812             }
2813             else if (cmatch)
2814             {
2815                 /*
2816                     match failed, so we have to copy what matched before
2817                     falling through and copying this character.  In reality,
2818                     this will only ever be just the newline character, but
2819                     it doesn't hurt to be precise.
2820                 */
2821                 strncpy(bp, cont_seq, cmatch);
2822                 bp += cmatch;
2823                 buf_len += cmatch;
2824                 cmatch = 0;
2825             }
2826         }
2827
2828         // copy this char
2829         *bp++ = data[i];
2830         buf_len++;
2831     }
2832
2833         buf[buf_len] = NULLCHAR;
2834 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2835         next_out = 0;
2836         leftover_start = 0;
2837
2838         i = 0;
2839         while (i < buf_len) {
2840             /* Deal with part of the TELNET option negotiation
2841                protocol.  We refuse to do anything beyond the
2842                defaults, except that we allow the WILL ECHO option,
2843                which ICS uses to turn off password echoing when we are
2844                directly connected to it.  We reject this option
2845                if localLineEditing mode is on (always on in xboard)
2846                and we are talking to port 23, which might be a real
2847                telnet server that will try to keep WILL ECHO on permanently.
2848              */
2849             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2850                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2851                 unsigned char option;
2852                 oldi = i;
2853                 switch ((unsigned char) buf[++i]) {
2854                   case TN_WILL:
2855                     if (appData.debugMode)
2856                       fprintf(debugFP, "\n<WILL ");
2857                     switch (option = (unsigned char) buf[++i]) {
2858                       case TN_ECHO:
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "ECHO ");
2861                         /* Reply only if this is a change, according
2862                            to the protocol rules. */
2863                         if (remoteEchoOption) break;
2864                         if (appData.localLineEditing &&
2865                             atoi(appData.icsPort) == TN_PORT) {
2866                             TelnetRequest(TN_DONT, TN_ECHO);
2867                         } else {
2868                             EchoOff();
2869                             TelnetRequest(TN_DO, TN_ECHO);
2870                             remoteEchoOption = TRUE;
2871                         }
2872                         break;
2873                       default:
2874                         if (appData.debugMode)
2875                           fprintf(debugFP, "%d ", option);
2876                         /* Whatever this is, we don't want it. */
2877                         TelnetRequest(TN_DONT, option);
2878                         break;
2879                     }
2880                     break;
2881                   case TN_WONT:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<WONT ");
2884                     switch (option = (unsigned char) buf[++i]) {
2885                       case TN_ECHO:
2886                         if (appData.debugMode)
2887                           fprintf(debugFP, "ECHO ");
2888                         /* Reply only if this is a change, according
2889                            to the protocol rules. */
2890                         if (!remoteEchoOption) break;
2891                         EchoOn();
2892                         TelnetRequest(TN_DONT, TN_ECHO);
2893                         remoteEchoOption = FALSE;
2894                         break;
2895                       default:
2896                         if (appData.debugMode)
2897                           fprintf(debugFP, "%d ", (unsigned char) option);
2898                         /* Whatever this is, it must already be turned
2899                            off, because we never agree to turn on
2900                            anything non-default, so according to the
2901                            protocol rules, we don't reply. */
2902                         break;
2903                     }
2904                     break;
2905                   case TN_DO:
2906                     if (appData.debugMode)
2907                       fprintf(debugFP, "\n<DO ");
2908                     switch (option = (unsigned char) buf[++i]) {
2909                       default:
2910                         /* Whatever this is, we refuse to do it. */
2911                         if (appData.debugMode)
2912                           fprintf(debugFP, "%d ", option);
2913                         TelnetRequest(TN_WONT, option);
2914                         break;
2915                     }
2916                     break;
2917                   case TN_DONT:
2918                     if (appData.debugMode)
2919                       fprintf(debugFP, "\n<DONT ");
2920                     switch (option = (unsigned char) buf[++i]) {
2921                       default:
2922                         if (appData.debugMode)
2923                           fprintf(debugFP, "%d ", option);
2924                         /* Whatever this is, we are already not doing
2925                            it, because we never agree to do anything
2926                            non-default, so according to the protocol
2927                            rules, we don't reply. */
2928                         break;
2929                     }
2930                     break;
2931                   case TN_IAC:
2932                     if (appData.debugMode)
2933                       fprintf(debugFP, "\n<IAC ");
2934                     /* Doubled IAC; pass it through */
2935                     i--;
2936                     break;
2937                   default:
2938                     if (appData.debugMode)
2939                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2940                     /* Drop all other telnet commands on the floor */
2941                     break;
2942                 }
2943                 if (oldi > next_out)
2944                   SendToPlayer(&buf[next_out], oldi - next_out);
2945                 if (++i > next_out)
2946                   next_out = i;
2947                 continue;
2948             }
2949
2950             /* OK, this at least will *usually* work */
2951             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2952                 loggedOn = TRUE;
2953             }
2954
2955             if (loggedOn && !intfSet) {
2956                 if (ics_type == ICS_ICC) {
2957                   snprintf(str, MSG_SIZ,
2958                           "/set-quietly interface %s\n/set-quietly style 12\n",
2959                           programVersion);
2960                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2961                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2962                 } else if (ics_type == ICS_CHESSNET) {
2963                   snprintf(str, MSG_SIZ, "/style 12\n");
2964                 } else {
2965                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2966                   strcat(str, programVersion);
2967                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2968                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2969                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2970 #ifdef WIN32
2971                   strcat(str, "$iset nohighlight 1\n");
2972 #endif
2973                   strcat(str, "$iset lock 1\n$style 12\n");
2974                 }
2975                 SendToICS(str);
2976                 NotifyFrontendLogin();
2977                 intfSet = TRUE;
2978             }
2979
2980             if (started == STARTED_COMMENT) {
2981                 /* Accumulate characters in comment */
2982                 parse[parse_pos++] = buf[i];
2983                 if (buf[i] == '\n') {
2984                     parse[parse_pos] = NULLCHAR;
2985                     if(chattingPartner>=0) {
2986                         char mess[MSG_SIZ];
2987                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2988                         OutputChatMessage(chattingPartner, mess);
2989                         chattingPartner = -1;
2990                         next_out = i+1; // [HGM] suppress printing in ICS window
2991                     } else
2992                     if(!suppressKibitz) // [HGM] kibitz
2993                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2994                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2995                         int nrDigit = 0, nrAlph = 0, j;
2996                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2997                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2998                         parse[parse_pos] = NULLCHAR;
2999                         // try to be smart: if it does not look like search info, it should go to
3000                         // ICS interaction window after all, not to engine-output window.
3001                         for(j=0; j<parse_pos; j++) { // count letters and digits
3002                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3003                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3004                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3005                         }
3006                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3007                             int depth=0; float score;
3008                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3009                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3010                                 pvInfoList[forwardMostMove-1].depth = depth;
3011                                 pvInfoList[forwardMostMove-1].score = 100*score;
3012                             }
3013                             OutputKibitz(suppressKibitz, parse);
3014                         } else {
3015                             char tmp[MSG_SIZ];
3016                             if(gameMode == IcsObserving) // restore original ICS messages
3017                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3018                             else
3019                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3020                             SendToPlayer(tmp, strlen(tmp));
3021                         }
3022                         next_out = i+1; // [HGM] suppress printing in ICS window
3023                     }
3024                     started = STARTED_NONE;
3025                 } else {
3026                     /* Don't match patterns against characters in comment */
3027                     i++;
3028                     continue;
3029                 }
3030             }
3031             if (started == STARTED_CHATTER) {
3032                 if (buf[i] != '\n') {
3033                     /* Don't match patterns against characters in chatter */
3034                     i++;
3035                     continue;
3036                 }
3037                 started = STARTED_NONE;
3038                 if(suppressKibitz) next_out = i+1;
3039             }
3040
3041             /* Kludge to deal with rcmd protocol */
3042             if (firstTime && looking_at(buf, &i, "\001*")) {
3043                 DisplayFatalError(&buf[1], 0, 1);
3044                 continue;
3045             } else {
3046                 firstTime = FALSE;
3047             }
3048
3049             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3050                 ics_type = ICS_ICC;
3051                 ics_prefix = "/";
3052                 if (appData.debugMode)
3053                   fprintf(debugFP, "ics_type %d\n", ics_type);
3054                 continue;
3055             }
3056             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3057                 ics_type = ICS_FICS;
3058                 ics_prefix = "$";
3059                 if (appData.debugMode)
3060                   fprintf(debugFP, "ics_type %d\n", ics_type);
3061                 continue;
3062             }
3063             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3064                 ics_type = ICS_CHESSNET;
3065                 ics_prefix = "/";
3066                 if (appData.debugMode)
3067                   fprintf(debugFP, "ics_type %d\n", ics_type);
3068                 continue;
3069             }
3070
3071             if (!loggedOn &&
3072                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3073                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3074                  looking_at(buf, &i, "will be \"*\""))) {
3075               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3076               continue;
3077             }
3078
3079             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3080               char buf[MSG_SIZ];
3081               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3082               DisplayIcsInteractionTitle(buf);
3083               have_set_title = TRUE;
3084             }
3085
3086             /* skip finger notes */
3087             if (started == STARTED_NONE &&
3088                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3089                  (buf[i] == '1' && buf[i+1] == '0')) &&
3090                 buf[i+2] == ':' && buf[i+3] == ' ') {
3091               started = STARTED_CHATTER;
3092               i += 3;
3093               continue;
3094             }
3095
3096             oldi = i;
3097             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3098             if(appData.seekGraph) {
3099                 if(soughtPending && MatchSoughtLine(buf+i)) {
3100                     i = strstr(buf+i, "rated") - buf;
3101                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3102                     next_out = leftover_start = i;
3103                     started = STARTED_CHATTER;
3104                     suppressKibitz = TRUE;
3105                     continue;
3106                 }
3107                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3108                         && looking_at(buf, &i, "* ads displayed")) {
3109                     soughtPending = FALSE;
3110                     seekGraphUp = TRUE;
3111                     DrawSeekGraph();
3112                     continue;
3113                 }
3114                 if(appData.autoRefresh) {
3115                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3116                         int s = (ics_type == ICS_ICC); // ICC format differs
3117                         if(seekGraphUp)
3118                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3119                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3120                         looking_at(buf, &i, "*% "); // eat prompt
3121                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i; // suppress
3124                         continue;
3125                     }
3126                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3127                         char *p = star_match[0];
3128                         while(*p) {
3129                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3130                             while(*p && *p++ != ' '); // next
3131                         }
3132                         looking_at(buf, &i, "*% "); // eat prompt
3133                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3134                         next_out = i;
3135                         continue;
3136                     }
3137                 }
3138             }
3139
3140             /* skip formula vars */
3141             if (started == STARTED_NONE &&
3142                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3143               started = STARTED_CHATTER;
3144               i += 3;
3145               continue;
3146             }
3147
3148             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3149             if (appData.autoKibitz && started == STARTED_NONE &&
3150                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3151                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3152                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3153                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3154                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3155                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3156                         suppressKibitz = TRUE;
3157                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3158                         next_out = i;
3159                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3160                                 && (gameMode == IcsPlayingWhite)) ||
3161                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3162                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3163                             started = STARTED_CHATTER; // own kibitz we simply discard
3164                         else {
3165                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3166                             parse_pos = 0; parse[0] = NULLCHAR;
3167                             savingComment = TRUE;
3168                             suppressKibitz = gameMode != IcsObserving ? 2 :
3169                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3170                         }
3171                         continue;
3172                 } else
3173                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3174                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3175                          && atoi(star_match[0])) {
3176                     // suppress the acknowledgements of our own autoKibitz
3177                     char *p;
3178                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3179                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3180                     SendToPlayer(star_match[0], strlen(star_match[0]));
3181                     if(looking_at(buf, &i, "*% ")) // eat prompt
3182                         suppressKibitz = FALSE;
3183                     next_out = i;
3184                     continue;
3185                 }
3186             } // [HGM] kibitz: end of patch
3187
3188             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3189
3190             // [HGM] chat: intercept tells by users for which we have an open chat window
3191             channel = -1;
3192             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3193                                            looking_at(buf, &i, "* whispers:") ||
3194                                            looking_at(buf, &i, "* kibitzes:") ||
3195                                            looking_at(buf, &i, "* shouts:") ||
3196                                            looking_at(buf, &i, "* c-shouts:") ||
3197                                            looking_at(buf, &i, "--> * ") ||
3198                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3199                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3200                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3201                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3202                 int p;
3203                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3204                 chattingPartner = -1;
3205
3206                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3207                 for(p=0; p<MAX_CHAT; p++) {
3208                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3209                     talker[0] = '['; strcat(talker, "] ");
3210                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3211                     chattingPartner = p; break;
3212                     }
3213                 } else
3214                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3215                 for(p=0; p<MAX_CHAT; p++) {
3216                     if(!strcmp("kibitzes", chatPartner[p])) {
3217                         talker[0] = '['; strcat(talker, "] ");
3218                         chattingPartner = p; break;
3219                     }
3220                 } else
3221                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3222                 for(p=0; p<MAX_CHAT; p++) {
3223                     if(!strcmp("whispers", chatPartner[p])) {
3224                         talker[0] = '['; strcat(talker, "] ");
3225                         chattingPartner = p; break;
3226                     }
3227                 } else
3228                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3229                   if(buf[i-8] == '-' && buf[i-3] == 't')
3230                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3231                     if(!strcmp("c-shouts", chatPartner[p])) {
3232                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3233                         chattingPartner = p; break;
3234                     }
3235                   }
3236                   if(chattingPartner < 0)
3237                   for(p=0; p<MAX_CHAT; p++) {
3238                     if(!strcmp("shouts", chatPartner[p])) {
3239                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3240                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3241                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3242                         chattingPartner = p; break;
3243                     }
3244                   }
3245                 }
3246                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3247                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3248                     talker[0] = 0; Colorize(ColorTell, FALSE);
3249                     chattingPartner = p; break;
3250                 }
3251                 if(chattingPartner<0) i = oldi; else {
3252                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3253                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3254                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3255                     started = STARTED_COMMENT;
3256                     parse_pos = 0; parse[0] = NULLCHAR;
3257                     savingComment = 3 + chattingPartner; // counts as TRUE
3258                     suppressKibitz = TRUE;
3259                     continue;
3260                 }
3261             } // [HGM] chat: end of patch
3262
3263           backup = i;
3264             if (appData.zippyTalk || appData.zippyPlay) {
3265                 /* [DM] Backup address for color zippy lines */
3266 #if ZIPPY
3267                if (loggedOn == TRUE)
3268                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3269                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3270 #endif
3271             } // [DM] 'else { ' deleted
3272                 if (
3273                     /* Regular tells and says */
3274                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3275                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3276                     looking_at(buf, &i, "* says: ") ||
3277                     /* Don't color "message" or "messages" output */
3278                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3279                     looking_at(buf, &i, "*. * at *:*: ") ||
3280                     looking_at(buf, &i, "--* (*:*): ") ||
3281                     /* Message notifications (same color as tells) */
3282                     looking_at(buf, &i, "* has left a message ") ||
3283                     looking_at(buf, &i, "* just sent you a message:\n") ||
3284                     /* Whispers and kibitzes */
3285                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3286                     looking_at(buf, &i, "* kibitzes: ") ||
3287                     /* Channel tells */
3288                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3289
3290                   if (tkind == 1 && strchr(star_match[0], ':')) {
3291                       /* Avoid "tells you:" spoofs in channels */
3292                      tkind = 3;
3293                   }
3294                   if (star_match[0][0] == NULLCHAR ||
3295                       strchr(star_match[0], ' ') ||
3296                       (tkind == 3 && strchr(star_match[1], ' '))) {
3297                     /* Reject bogus matches */
3298                     i = oldi;
3299                   } else {
3300                     if (appData.colorize) {
3301                       if (oldi > next_out) {
3302                         SendToPlayer(&buf[next_out], oldi - next_out);
3303                         next_out = oldi;
3304                       }
3305                       switch (tkind) {
3306                       case 1:
3307                         Colorize(ColorTell, FALSE);
3308                         curColor = ColorTell;
3309                         break;
3310                       case 2:
3311                         Colorize(ColorKibitz, FALSE);
3312                         curColor = ColorKibitz;
3313                         break;
3314                       case 3:
3315                         p = strrchr(star_match[1], '(');
3316                         if (p == NULL) {
3317                           p = star_match[1];
3318                         } else {
3319                           p++;
3320                         }
3321                         if (atoi(p) == 1) {
3322                           Colorize(ColorChannel1, FALSE);
3323                           curColor = ColorChannel1;
3324                         } else {
3325                           Colorize(ColorChannel, FALSE);
3326                           curColor = ColorChannel;
3327                         }
3328                         break;
3329                       case 5:
3330                         curColor = ColorNormal;
3331                         break;
3332                       }
3333                     }
3334                     if (started == STARTED_NONE && appData.autoComment &&
3335                         (gameMode == IcsObserving ||
3336                          gameMode == IcsPlayingWhite ||
3337                          gameMode == IcsPlayingBlack)) {
3338                       parse_pos = i - oldi;
3339                       memcpy(parse, &buf[oldi], parse_pos);
3340                       parse[parse_pos] = NULLCHAR;
3341                       started = STARTED_COMMENT;
3342                       savingComment = TRUE;
3343                     } else {
3344                       started = STARTED_CHATTER;
3345                       savingComment = FALSE;
3346                     }
3347                     loggedOn = TRUE;
3348                     continue;
3349                   }
3350                 }
3351
3352                 if (looking_at(buf, &i, "* s-shouts: ") ||
3353                     looking_at(buf, &i, "* c-shouts: ")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorSShout, FALSE);
3360                         curColor = ColorSShout;
3361                     }
3362                     loggedOn = TRUE;
3363                     started = STARTED_CHATTER;
3364                     continue;
3365                 }
3366
3367                 if (looking_at(buf, &i, "--->")) {
3368                     loggedOn = TRUE;
3369                     continue;
3370                 }
3371
3372                 if (looking_at(buf, &i, "* shouts: ") ||
3373                     looking_at(buf, &i, "--> ")) {
3374                     if (appData.colorize) {
3375                         if (oldi > next_out) {
3376                             SendToPlayer(&buf[next_out], oldi - next_out);
3377                             next_out = oldi;
3378                         }
3379                         Colorize(ColorShout, FALSE);
3380                         curColor = ColorShout;
3381                     }
3382                     loggedOn = TRUE;
3383                     started = STARTED_CHATTER;
3384                     continue;
3385                 }
3386
3387                 if (looking_at( buf, &i, "Challenge:")) {
3388                     if (appData.colorize) {
3389                         if (oldi > next_out) {
3390                             SendToPlayer(&buf[next_out], oldi - next_out);
3391                             next_out = oldi;
3392                         }
3393                         Colorize(ColorChallenge, FALSE);
3394                         curColor = ColorChallenge;
3395                     }
3396                     loggedOn = TRUE;
3397                     continue;
3398                 }
3399
3400                 if (looking_at(buf, &i, "* offers you") ||
3401                     looking_at(buf, &i, "* offers to be") ||
3402                     looking_at(buf, &i, "* would like to") ||
3403                     looking_at(buf, &i, "* requests to") ||
3404                     looking_at(buf, &i, "Your opponent offers") ||
3405                     looking_at(buf, &i, "Your opponent requests")) {
3406
3407                     if (appData.colorize) {
3408                         if (oldi > next_out) {
3409                             SendToPlayer(&buf[next_out], oldi - next_out);
3410                             next_out = oldi;
3411                         }
3412                         Colorize(ColorRequest, FALSE);
3413                         curColor = ColorRequest;
3414                     }
3415                     continue;
3416                 }
3417
3418                 if (looking_at(buf, &i, "* (*) seeking")) {
3419                     if (appData.colorize) {
3420                         if (oldi > next_out) {
3421                             SendToPlayer(&buf[next_out], oldi - next_out);
3422                             next_out = oldi;
3423                         }
3424                         Colorize(ColorSeek, FALSE);
3425                         curColor = ColorSeek;
3426                     }
3427                     continue;
3428             }
3429
3430           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3431
3432             if (looking_at(buf, &i, "\\   ")) {
3433                 if (prevColor != ColorNormal) {
3434                     if (oldi > next_out) {
3435                         SendToPlayer(&buf[next_out], oldi - next_out);
3436                         next_out = oldi;
3437                     }
3438                     Colorize(prevColor, TRUE);
3439                     curColor = prevColor;
3440                 }
3441                 if (savingComment) {
3442                     parse_pos = i - oldi;
3443                     memcpy(parse, &buf[oldi], parse_pos);
3444                     parse[parse_pos] = NULLCHAR;
3445                     started = STARTED_COMMENT;
3446                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3447                         chattingPartner = savingComment - 3; // kludge to remember the box
3448                 } else {
3449                     started = STARTED_CHATTER;
3450                 }
3451                 continue;
3452             }
3453
3454             if (looking_at(buf, &i, "Black Strength :") ||
3455                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3456                 looking_at(buf, &i, "<10>") ||
3457                 looking_at(buf, &i, "#@#")) {
3458                 /* Wrong board style */
3459                 loggedOn = TRUE;
3460                 SendToICS(ics_prefix);
3461                 SendToICS("set style 12\n");
3462                 SendToICS(ics_prefix);
3463                 SendToICS("refresh\n");
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "login:")) {
3468               if (!have_sent_ICS_logon) {
3469                 if(ICSInitScript())
3470                   have_sent_ICS_logon = 1;
3471                 else // no init script was found
3472                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3473               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3474                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3475               }
3476                 continue;
3477             }
3478
3479             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3480                 (looking_at(buf, &i, "\n<12> ") ||
3481                  looking_at(buf, &i, "<12> "))) {
3482                 loggedOn = TRUE;
3483                 if (oldi > next_out) {
3484                     SendToPlayer(&buf[next_out], oldi - next_out);
3485                 }
3486                 next_out = i;
3487                 started = STARTED_BOARD;
3488                 parse_pos = 0;
3489                 continue;
3490             }
3491
3492             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3493                 looking_at(buf, &i, "<b1> ")) {
3494                 if (oldi > next_out) {
3495                     SendToPlayer(&buf[next_out], oldi - next_out);
3496                 }
3497                 next_out = i;
3498                 started = STARTED_HOLDINGS;
3499                 parse_pos = 0;
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3504                 loggedOn = TRUE;
3505                 /* Header for a move list -- first line */
3506
3507                 switch (ics_getting_history) {
3508                   case H_FALSE:
3509                     switch (gameMode) {
3510                       case IcsIdle:
3511                       case BeginningOfGame:
3512                         /* User typed "moves" or "oldmoves" while we
3513                            were idle.  Pretend we asked for these
3514                            moves and soak them up so user can step
3515                            through them and/or save them.
3516                            */
3517                         Reset(FALSE, TRUE);
3518                         gameMode = IcsObserving;
3519                         ModeHighlight();
3520                         ics_gamenum = -1;
3521                         ics_getting_history = H_GOT_UNREQ_HEADER;
3522                         break;
3523                       case EditGame: /*?*/
3524                       case EditPosition: /*?*/
3525                         /* Should above feature work in these modes too? */
3526                         /* For now it doesn't */
3527                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3528                         break;
3529                       default:
3530                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3531                         break;
3532                     }
3533                     break;
3534                   case H_REQUESTED:
3535                     /* Is this the right one? */
3536                     if (gameInfo.white && gameInfo.black &&
3537                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3538                         strcmp(gameInfo.black, star_match[2]) == 0) {
3539                         /* All is well */
3540                         ics_getting_history = H_GOT_REQ_HEADER;
3541                     }
3542                     break;
3543                   case H_GOT_REQ_HEADER:
3544                   case H_GOT_UNREQ_HEADER:
3545                   case H_GOT_UNWANTED_HEADER:
3546                   case H_GETTING_MOVES:
3547                     /* Should not happen */
3548                     DisplayError(_("Error gathering move list: two headers"), 0);
3549                     ics_getting_history = H_FALSE;
3550                     break;
3551                 }
3552
3553                 /* Save player ratings into gameInfo if needed */
3554                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3555                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3556                     (gameInfo.whiteRating == -1 ||
3557                      gameInfo.blackRating == -1)) {
3558
3559                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3560                     gameInfo.blackRating = string_to_rating(star_match[3]);
3561                     if (appData.debugMode)
3562                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3563                               gameInfo.whiteRating, gameInfo.blackRating);
3564                 }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i,
3569               "* * match, initial time: * minute*, increment: * second")) {
3570                 /* Header for a move list -- second line */
3571                 /* Initial board will follow if this is a wild game */
3572                 if (gameInfo.event != NULL) free(gameInfo.event);
3573                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3574                 gameInfo.event = StrSave(str);
3575                 /* [HGM] we switched variant. Translate boards if needed. */
3576                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3577                 continue;
3578             }
3579
3580             if (looking_at(buf, &i, "Move  ")) {
3581                 /* Beginning of a move list */
3582                 switch (ics_getting_history) {
3583                   case H_FALSE:
3584                     /* Normally should not happen */
3585                     /* Maybe user hit reset while we were parsing */
3586                     break;
3587                   case H_REQUESTED:
3588                     /* Happens if we are ignoring a move list that is not
3589                      * the one we just requested.  Common if the user
3590                      * tries to observe two games without turning off
3591                      * getMoveList */
3592                     break;
3593                   case H_GETTING_MOVES:
3594                     /* Should not happen */
3595                     DisplayError(_("Error gathering move list: nested"), 0);
3596                     ics_getting_history = H_FALSE;
3597                     break;
3598                   case H_GOT_REQ_HEADER:
3599                     ics_getting_history = H_GETTING_MOVES;
3600                     started = STARTED_MOVES;
3601                     parse_pos = 0;
3602                     if (oldi > next_out) {
3603                         SendToPlayer(&buf[next_out], oldi - next_out);
3604                     }
3605                     break;
3606                   case H_GOT_UNREQ_HEADER:
3607                     ics_getting_history = H_GETTING_MOVES;
3608                     started = STARTED_MOVES_NOHIDE;
3609                     parse_pos = 0;
3610                     break;
3611                   case H_GOT_UNWANTED_HEADER:
3612                     ics_getting_history = H_FALSE;
3613                     break;
3614                 }
3615                 continue;
3616             }
3617
3618             if (looking_at(buf, &i, "% ") ||
3619                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3620                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3621                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3622                     soughtPending = FALSE;
3623                     seekGraphUp = TRUE;
3624                     DrawSeekGraph();
3625                 }
3626                 if(suppressKibitz) next_out = i;
3627                 savingComment = FALSE;
3628                 suppressKibitz = 0;
3629                 switch (started) {
3630                   case STARTED_MOVES:
3631                   case STARTED_MOVES_NOHIDE:
3632                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3633                     parse[parse_pos + i - oldi] = NULLCHAR;
3634                     ParseGameHistory(parse);
3635 #if ZIPPY
3636                     if (appData.zippyPlay && first.initDone) {
3637                         FeedMovesToProgram(&first, forwardMostMove);
3638                         if (gameMode == IcsPlayingWhite) {
3639                             if (WhiteOnMove(forwardMostMove)) {
3640                                 if (first.sendTime) {
3641                                   if (first.useColors) {
3642                                     SendToProgram("black\n", &first);
3643                                   }
3644                                   SendTimeRemaining(&first, TRUE);
3645                                 }
3646                                 if (first.useColors) {
3647                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3648                                 }
3649                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3650                                 first.maybeThinking = TRUE;
3651                             } else {
3652                                 if (first.usePlayother) {
3653                                   if (first.sendTime) {
3654                                     SendTimeRemaining(&first, TRUE);
3655                                   }
3656                                   SendToProgram("playother\n", &first);
3657                                   firstMove = FALSE;
3658                                 } else {
3659                                   firstMove = TRUE;
3660                                 }
3661                             }
3662                         } else if (gameMode == IcsPlayingBlack) {
3663                             if (!WhiteOnMove(forwardMostMove)) {
3664                                 if (first.sendTime) {
3665                                   if (first.useColors) {
3666                                     SendToProgram("white\n", &first);
3667                                   }
3668                                   SendTimeRemaining(&first, FALSE);
3669                                 }
3670                                 if (first.useColors) {
3671                                   SendToProgram("black\n", &first);
3672                                 }
3673                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3674                                 first.maybeThinking = TRUE;
3675                             } else {
3676                                 if (first.usePlayother) {
3677                                   if (first.sendTime) {
3678                                     SendTimeRemaining(&first, FALSE);
3679                                   }
3680                                   SendToProgram("playother\n", &first);
3681                                   firstMove = FALSE;
3682                                 } else {
3683                                   firstMove = TRUE;
3684                                 }
3685                             }
3686                         }
3687                     }
3688 #endif
3689                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3690                         /* Moves came from oldmoves or moves command
3691                            while we weren't doing anything else.
3692                            */
3693                         currentMove = forwardMostMove;
3694                         ClearHighlights();/*!!could figure this out*/
3695                         flipView = appData.flipView;
3696                         DrawPosition(TRUE, boards[currentMove]);
3697                         DisplayBothClocks();
3698                         snprintf(str, MSG_SIZ, "%s %s %s",
3699                                 gameInfo.white, _("vs."),  gameInfo.black);
3700                         DisplayTitle(str);
3701                         gameMode = IcsIdle;
3702                     } else {
3703                         /* Moves were history of an active game */
3704                         if (gameInfo.resultDetails != NULL) {
3705                             free(gameInfo.resultDetails);
3706                             gameInfo.resultDetails = NULL;
3707                         }
3708                     }
3709                     HistorySet(parseList, backwardMostMove,
3710                                forwardMostMove, currentMove-1);
3711                     DisplayMove(currentMove - 1);
3712                     if (started == STARTED_MOVES) next_out = i;
3713                     started = STARTED_NONE;
3714                     ics_getting_history = H_FALSE;
3715                     break;
3716
3717                   case STARTED_OBSERVE:
3718                     started = STARTED_NONE;
3719                     SendToICS(ics_prefix);
3720                     SendToICS("refresh\n");
3721                     break;
3722
3723                   default:
3724                     break;
3725                 }
3726                 if(bookHit) { // [HGM] book: simulate book reply
3727                     static char bookMove[MSG_SIZ]; // a bit generous?
3728
3729                     programStats.nodes = programStats.depth = programStats.time =
3730                     programStats.score = programStats.got_only_move = 0;
3731                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3732
3733                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3734                     strcat(bookMove, bookHit);
3735                     HandleMachineMove(bookMove, &first);
3736                 }
3737                 continue;
3738             }
3739
3740             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3741                  started == STARTED_HOLDINGS ||
3742                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3743                 /* Accumulate characters in move list or board */
3744                 parse[parse_pos++] = buf[i];
3745             }
3746
3747             /* Start of game messages.  Mostly we detect start of game
3748                when the first board image arrives.  On some versions
3749                of the ICS, though, we need to do a "refresh" after starting
3750                to observe in order to get the current board right away. */
3751             if (looking_at(buf, &i, "Adding game * to observation list")) {
3752                 started = STARTED_OBSERVE;
3753                 continue;
3754             }
3755
3756             /* Handle auto-observe */
3757             if (appData.autoObserve &&
3758                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3759                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3760                 char *player;
3761                 /* Choose the player that was highlighted, if any. */
3762                 if (star_match[0][0] == '\033' ||
3763                     star_match[1][0] != '\033') {
3764                     player = star_match[0];
3765                 } else {
3766                     player = star_match[2];
3767                 }
3768                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3769                         ics_prefix, StripHighlightAndTitle(player));
3770                 SendToICS(str);
3771
3772                 /* Save ratings from notify string */
3773                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3774                 player1Rating = string_to_rating(star_match[1]);
3775                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3776                 player2Rating = string_to_rating(star_match[3]);
3777
3778                 if (appData.debugMode)
3779                   fprintf(debugFP,
3780                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3781                           player1Name, player1Rating,
3782                           player2Name, player2Rating);
3783
3784                 continue;
3785             }
3786
3787             /* Deal with automatic examine mode after a game,
3788                and with IcsObserving -> IcsExamining transition */
3789             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3790                 looking_at(buf, &i, "has made you an examiner of game *")) {
3791
3792                 int gamenum = atoi(star_match[0]);
3793                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3794                     gamenum == ics_gamenum) {
3795                     /* We were already playing or observing this game;
3796                        no need to refetch history */
3797                     gameMode = IcsExamining;
3798                     if (pausing) {
3799                         pauseExamForwardMostMove = forwardMostMove;
3800                     } else if (currentMove < forwardMostMove) {
3801                         ForwardInner(forwardMostMove);
3802                     }
3803                 } else {
3804                     /* I don't think this case really can happen */
3805                     SendToICS(ics_prefix);
3806                     SendToICS("refresh\n");
3807                 }
3808                 continue;
3809             }
3810
3811             /* Error messages */
3812 //          if (ics_user_moved) {
3813             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3814                 if (looking_at(buf, &i, "Illegal move") ||
3815                     looking_at(buf, &i, "Not a legal move") ||
3816                     looking_at(buf, &i, "Your king is in check") ||
3817                     looking_at(buf, &i, "It isn't your turn") ||
3818                     looking_at(buf, &i, "It is not your move")) {
3819                     /* Illegal move */
3820                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3821                         currentMove = forwardMostMove-1;
3822                         DisplayMove(currentMove - 1); /* before DMError */
3823                         DrawPosition(FALSE, boards[currentMove]);
3824                         SwitchClocks(forwardMostMove-1); // [HGM] race
3825                         DisplayBothClocks();
3826                     }
3827                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3828                     ics_user_moved = 0;
3829                     continue;
3830                 }
3831             }
3832
3833             if (looking_at(buf, &i, "still have time") ||
3834                 looking_at(buf, &i, "not out of time") ||
3835                 looking_at(buf, &i, "either player is out of time") ||
3836                 looking_at(buf, &i, "has timeseal; checking")) {
3837                 /* We must have called his flag a little too soon */
3838                 whiteFlag = blackFlag = FALSE;
3839                 continue;
3840             }
3841
3842             if (looking_at(buf, &i, "added * seconds to") ||
3843                 looking_at(buf, &i, "seconds were added to")) {
3844                 /* Update the clocks */
3845                 SendToICS(ics_prefix);
3846                 SendToICS("refresh\n");
3847                 continue;
3848             }
3849
3850             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3851                 ics_clock_paused = TRUE;
3852                 StopClocks();
3853                 continue;
3854             }
3855
3856             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3857                 ics_clock_paused = FALSE;
3858                 StartClocks();
3859                 continue;
3860             }
3861
3862             /* Grab player ratings from the Creating: message.
3863                Note we have to check for the special case when
3864                the ICS inserts things like [white] or [black]. */
3865             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3866                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3867                 /* star_matches:
3868                    0    player 1 name (not necessarily white)
3869                    1    player 1 rating
3870                    2    empty, white, or black (IGNORED)
3871                    3    player 2 name (not necessarily black)
3872                    4    player 2 rating
3873
3874                    The names/ratings are sorted out when the game
3875                    actually starts (below).
3876                 */
3877                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3878                 player1Rating = string_to_rating(star_match[1]);
3879                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3880                 player2Rating = string_to_rating(star_match[4]);
3881
3882                 if (appData.debugMode)
3883                   fprintf(debugFP,
3884                           "Ratings from 'Creating:' %s %d, %s %d\n",
3885                           player1Name, player1Rating,
3886                           player2Name, player2Rating);
3887
3888                 continue;
3889             }
3890
3891             /* Improved generic start/end-of-game messages */
3892             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3893                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3894                 /* If tkind == 0: */
3895                 /* star_match[0] is the game number */
3896                 /*           [1] is the white player's name */
3897                 /*           [2] is the black player's name */
3898                 /* For end-of-game: */
3899                 /*           [3] is the reason for the game end */
3900                 /*           [4] is a PGN end game-token, preceded by " " */
3901                 /* For start-of-game: */
3902                 /*           [3] begins with "Creating" or "Continuing" */
3903                 /*           [4] is " *" or empty (don't care). */
3904                 int gamenum = atoi(star_match[0]);
3905                 char *whitename, *blackname, *why, *endtoken;
3906                 ChessMove endtype = EndOfFile;
3907
3908                 if (tkind == 0) {
3909                   whitename = star_match[1];
3910                   blackname = star_match[2];
3911                   why = star_match[3];
3912                   endtoken = star_match[4];
3913                 } else {
3914                   whitename = star_match[1];
3915                   blackname = star_match[3];
3916                   why = star_match[5];
3917                   endtoken = star_match[6];
3918                 }
3919
3920                 /* Game start messages */
3921                 if (strncmp(why, "Creating ", 9) == 0 ||
3922                     strncmp(why, "Continuing ", 11) == 0) {
3923                     gs_gamenum = gamenum;
3924                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3925                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3926                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3927 #if ZIPPY
3928                     if (appData.zippyPlay) {
3929                         ZippyGameStart(whitename, blackname);
3930                     }
3931 #endif /*ZIPPY*/
3932                     partnerBoardValid = FALSE; // [HGM] bughouse
3933                     continue;
3934                 }
3935
3936                 /* Game end messages */
3937                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3938                     ics_gamenum != gamenum) {
3939                     continue;
3940                 }
3941                 while (endtoken[0] == ' ') endtoken++;
3942                 switch (endtoken[0]) {
3943                   case '*':
3944                   default:
3945                     endtype = GameUnfinished;
3946                     break;
3947                   case '0':
3948                     endtype = BlackWins;
3949                     break;
3950                   case '1':
3951                     if (endtoken[1] == '/')
3952                       endtype = GameIsDrawn;
3953                     else
3954                       endtype = WhiteWins;
3955                     break;
3956                 }
3957                 GameEnds(endtype, why, GE_ICS);
3958 #if ZIPPY
3959                 if (appData.zippyPlay && first.initDone) {
3960                     ZippyGameEnd(endtype, why);
3961                     if (first.pr == NoProc) {
3962                       /* Start the next process early so that we'll
3963                          be ready for the next challenge */
3964                       StartChessProgram(&first);
3965                     }
3966                     /* Send "new" early, in case this command takes
3967                        a long time to finish, so that we'll be ready
3968                        for the next challenge. */
3969                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3970                     Reset(TRUE, TRUE);
3971                 }
3972 #endif /*ZIPPY*/
3973                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3974                 continue;
3975             }
3976
3977             if (looking_at(buf, &i, "Removing game * from observation") ||
3978                 looking_at(buf, &i, "no longer observing game *") ||
3979                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3980                 if (gameMode == IcsObserving &&
3981                     atoi(star_match[0]) == ics_gamenum)
3982                   {
3983                       /* icsEngineAnalyze */
3984                       if (appData.icsEngineAnalyze) {
3985                             ExitAnalyzeMode();
3986                             ModeHighlight();
3987                       }
3988                       StopClocks();
3989                       gameMode = IcsIdle;
3990                       ics_gamenum = -1;
3991                       ics_user_moved = FALSE;
3992                   }
3993                 continue;
3994             }
3995
3996             if (looking_at(buf, &i, "no longer examining game *")) {
3997                 if (gameMode == IcsExamining &&
3998                     atoi(star_match[0]) == ics_gamenum)
3999                   {
4000                       gameMode = IcsIdle;
4001                       ics_gamenum = -1;
4002                       ics_user_moved = FALSE;
4003                   }
4004                 continue;
4005             }
4006
4007             /* Advance leftover_start past any newlines we find,
4008                so only partial lines can get reparsed */
4009             if (looking_at(buf, &i, "\n")) {
4010                 prevColor = curColor;
4011                 if (curColor != ColorNormal) {
4012                     if (oldi > next_out) {
4013                         SendToPlayer(&buf[next_out], oldi - next_out);
4014                         next_out = oldi;
4015                     }
4016                     Colorize(ColorNormal, FALSE);
4017                     curColor = ColorNormal;
4018                 }
4019                 if (started == STARTED_BOARD) {
4020                     started = STARTED_NONE;
4021                     parse[parse_pos] = NULLCHAR;
4022                     ParseBoard12(parse);
4023                     ics_user_moved = 0;
4024
4025                     /* Send premove here */
4026                     if (appData.premove) {
4027                       char str[MSG_SIZ];
4028                       if (currentMove == 0 &&
4029                           gameMode == IcsPlayingWhite &&
4030                           appData.premoveWhite) {
4031                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4032                         if (appData.debugMode)
4033                           fprintf(debugFP, "Sending premove:\n");
4034                         SendToICS(str);
4035                       } else if (currentMove == 1 &&
4036                                  gameMode == IcsPlayingBlack &&
4037                                  appData.premoveBlack) {
4038                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4039                         if (appData.debugMode)
4040                           fprintf(debugFP, "Sending premove:\n");
4041                         SendToICS(str);
4042                       } else if (gotPremove) {
4043                         gotPremove = 0;
4044                         ClearPremoveHighlights();
4045                         if (appData.debugMode)
4046                           fprintf(debugFP, "Sending premove:\n");
4047                           UserMoveEvent(premoveFromX, premoveFromY,
4048                                         premoveToX, premoveToY,
4049                                         premovePromoChar);
4050                       }
4051                     }
4052
4053                     /* Usually suppress following prompt */
4054                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4055                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4056                         if (looking_at(buf, &i, "*% ")) {
4057                             savingComment = FALSE;
4058                             suppressKibitz = 0;
4059                         }
4060                     }
4061                     next_out = i;
4062                 } else if (started == STARTED_HOLDINGS) {
4063                     int gamenum;
4064                     char new_piece[MSG_SIZ];
4065                     started = STARTED_NONE;
4066                     parse[parse_pos] = NULLCHAR;
4067                     if (appData.debugMode)
4068                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4069                                                         parse, currentMove);
4070                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4071                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4072                         if (gameInfo.variant == VariantNormal) {
4073                           /* [HGM] We seem to switch variant during a game!
4074                            * Presumably no holdings were displayed, so we have
4075                            * to move the position two files to the right to
4076                            * create room for them!
4077                            */
4078                           VariantClass newVariant;
4079                           switch(gameInfo.boardWidth) { // base guess on board width
4080                                 case 9:  newVariant = VariantShogi; break;
4081                                 case 10: newVariant = VariantGreat; break;
4082                                 default: newVariant = VariantCrazyhouse; break;
4083                           }
4084                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4085                           /* Get a move list just to see the header, which
4086                              will tell us whether this is really bug or zh */
4087                           if (ics_getting_history == H_FALSE) {
4088                             ics_getting_history = H_REQUESTED;
4089                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4090                             SendToICS(str);
4091                           }
4092                         }
4093                         new_piece[0] = NULLCHAR;
4094                         sscanf(parse, "game %d white [%s black [%s <- %s",
4095                                &gamenum, white_holding, black_holding,
4096                                new_piece);
4097                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4098                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4099                         /* [HGM] copy holdings to board holdings area */
4100                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4101                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4102                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4103 #if ZIPPY
4104                         if (appData.zippyPlay && first.initDone) {
4105                             ZippyHoldings(white_holding, black_holding,
4106                                           new_piece);
4107                         }
4108 #endif /*ZIPPY*/
4109                         if (tinyLayout || smallLayout) {
4110                             char wh[16], bh[16];
4111                             PackHolding(wh, white_holding);
4112                             PackHolding(bh, black_holding);
4113                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4114                                     gameInfo.white, gameInfo.black);
4115                         } else {
4116                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4117                                     gameInfo.white, white_holding, _("vs."),
4118                                     gameInfo.black, black_holding);
4119                         }
4120                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4121                         DrawPosition(FALSE, boards[currentMove]);
4122                         DisplayTitle(str);
4123                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4124                         sscanf(parse, "game %d white [%s black [%s <- %s",
4125                                &gamenum, white_holding, black_holding,
4126                                new_piece);
4127                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4128                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4129                         /* [HGM] copy holdings to partner-board holdings area */
4130                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4131                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4132                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4133                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4134                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4135                       }
4136                     }
4137                     /* Suppress following prompt */
4138                     if (looking_at(buf, &i, "*% ")) {
4139                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4140                         savingComment = FALSE;
4141                         suppressKibitz = 0;
4142                     }
4143                     next_out = i;
4144                 }
4145                 continue;
4146             }
4147
4148             i++;                /* skip unparsed character and loop back */
4149         }
4150
4151         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4152 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4153 //          SendToPlayer(&buf[next_out], i - next_out);
4154             started != STARTED_HOLDINGS && leftover_start > next_out) {
4155             SendToPlayer(&buf[next_out], leftover_start - next_out);
4156             next_out = i;
4157         }
4158
4159         leftover_len = buf_len - leftover_start;
4160         /* if buffer ends with something we couldn't parse,
4161            reparse it after appending the next read */
4162
4163     } else if (count == 0) {
4164         RemoveInputSource(isr);
4165         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4166     } else {
4167         DisplayFatalError(_("Error reading from ICS"), error, 1);
4168     }
4169 }
4170
4171
4172 /* Board style 12 looks like this:
4173
4174    <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
4175
4176  * The "<12> " is stripped before it gets to this routine.  The two
4177  * trailing 0's (flip state and clock ticking) are later addition, and
4178  * some chess servers may not have them, or may have only the first.
4179  * Additional trailing fields may be added in the future.
4180  */
4181
4182 #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"
4183
4184 #define RELATION_OBSERVING_PLAYED    0
4185 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4186 #define RELATION_PLAYING_MYMOVE      1
4187 #define RELATION_PLAYING_NOTMYMOVE  -1
4188 #define RELATION_EXAMINING           2
4189 #define RELATION_ISOLATED_BOARD     -3
4190 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4191
4192 void
4193 ParseBoard12 (char *string)
4194 {
4195 #if ZIPPY
4196     int i, takeback;
4197     char *bookHit = NULL; // [HGM] book
4198 #endif
4199     GameMode newGameMode;
4200     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4201     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4202     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4203     char to_play, board_chars[200];
4204     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4205     char black[32], white[32];
4206     Board board;
4207     int prevMove = currentMove;
4208     int ticking = 2;
4209     ChessMove moveType;
4210     int fromX, fromY, toX, toY;
4211     char promoChar;
4212     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4213     Boolean weird = FALSE, reqFlag = FALSE;
4214
4215     fromX = fromY = toX = toY = -1;
4216
4217     newGame = FALSE;
4218
4219     if (appData.debugMode)
4220       fprintf(debugFP, "Parsing board: %s\n", string);
4221
4222     move_str[0] = NULLCHAR;
4223     elapsed_time[0] = NULLCHAR;
4224     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4225         int  i = 0, j;
4226         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4227             if(string[i] == ' ') { ranks++; files = 0; }
4228             else files++;
4229             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4230             i++;
4231         }
4232         for(j = 0; j <i; j++) board_chars[j] = string[j];
4233         board_chars[i] = '\0';
4234         string += i + 1;
4235     }
4236     n = sscanf(string, PATTERN, &to_play, &double_push,
4237                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4238                &gamenum, white, black, &relation, &basetime, &increment,
4239                &white_stren, &black_stren, &white_time, &black_time,
4240                &moveNum, str, elapsed_time, move_str, &ics_flip,
4241                &ticking);
4242
4243     if (n < 21) {
4244         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4245         DisplayError(str, 0);
4246         return;
4247     }
4248
4249     /* Convert the move number to internal form */
4250     moveNum = (moveNum - 1) * 2;
4251     if (to_play == 'B') moveNum++;
4252     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4253       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4254                         0, 1);
4255       return;
4256     }
4257
4258     switch (relation) {
4259       case RELATION_OBSERVING_PLAYED:
4260       case RELATION_OBSERVING_STATIC:
4261         if (gamenum == -1) {
4262             /* Old ICC buglet */
4263             relation = RELATION_OBSERVING_STATIC;
4264         }
4265         newGameMode = IcsObserving;
4266         break;
4267       case RELATION_PLAYING_MYMOVE:
4268       case RELATION_PLAYING_NOTMYMOVE:
4269         newGameMode =
4270           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4271             IcsPlayingWhite : IcsPlayingBlack;
4272         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4273         break;
4274       case RELATION_EXAMINING:
4275         newGameMode = IcsExamining;
4276         break;
4277       case RELATION_ISOLATED_BOARD:
4278       default:
4279         /* Just display this board.  If user was doing something else,
4280            we will forget about it until the next board comes. */
4281         newGameMode = IcsIdle;
4282         break;
4283       case RELATION_STARTING_POSITION:
4284         newGameMode = gameMode;
4285         break;
4286     }
4287
4288     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4289         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4290          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4291       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4292       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4293       static int lastBgGame = -1;
4294       char *toSqr;
4295       for (k = 0; k < ranks; k++) {
4296         for (j = 0; j < files; j++)
4297           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4298         if(gameInfo.holdingsWidth > 1) {
4299              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4300              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4301         }
4302       }
4303       CopyBoard(partnerBoard, board);
4304       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4305         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4306         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4307       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4308       if(toSqr = strchr(str, '-')) {
4309         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4310         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4311       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4312       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4313       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4314       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4315       if(twoBoards) {
4316           DisplayWhiteClock(white_time*fac, to_play == 'W');
4317           DisplayBlackClock(black_time*fac, to_play != 'W');
4318           activePartner = to_play;
4319           if(gamenum != lastBgGame) {
4320               char buf[MSG_SIZ];
4321               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4322               DisplayTitle(buf);
4323           }
4324           lastBgGame = gamenum;
4325           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4326                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4327       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4328                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4329       if(!twoBoards) DisplayMessage(partnerStatus, "");
4330         partnerBoardValid = TRUE;
4331       return;
4332     }
4333
4334     if(appData.dualBoard && appData.bgObserve) {
4335         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4336             SendToICS(ics_prefix), SendToICS("pobserve\n");
4337         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4338             char buf[MSG_SIZ];
4339             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4340             SendToICS(buf);
4341         }
4342     }
4343
4344     /* Modify behavior for initial board display on move listing
4345        of wild games.
4346        */
4347     switch (ics_getting_history) {
4348       case H_FALSE:
4349       case H_REQUESTED:
4350         break;
4351       case H_GOT_REQ_HEADER:
4352       case H_GOT_UNREQ_HEADER:
4353         /* This is the initial position of the current game */
4354         gamenum = ics_gamenum;
4355         moveNum = 0;            /* old ICS bug workaround */
4356         if (to_play == 'B') {
4357           startedFromSetupPosition = TRUE;
4358           blackPlaysFirst = TRUE;
4359           moveNum = 1;
4360           if (forwardMostMove == 0) forwardMostMove = 1;
4361           if (backwardMostMove == 0) backwardMostMove = 1;
4362           if (currentMove == 0) currentMove = 1;
4363         }
4364         newGameMode = gameMode;
4365         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4366         break;
4367       case H_GOT_UNWANTED_HEADER:
4368         /* This is an initial board that we don't want */
4369         return;
4370       case H_GETTING_MOVES:
4371         /* Should not happen */
4372         DisplayError(_("Error gathering move list: extra board"), 0);
4373         ics_getting_history = H_FALSE;
4374         return;
4375     }
4376
4377    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4378                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4379                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4380      /* [HGM] We seem to have switched variant unexpectedly
4381       * Try to guess new variant from board size
4382       */
4383           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4384           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4385           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4386           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4387           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4388           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4389           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4390           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4391           /* Get a move list just to see the header, which
4392              will tell us whether this is really bug or zh */
4393           if (ics_getting_history == H_FALSE) {
4394             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4395             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4396             SendToICS(str);
4397           }
4398     }
4399
4400     /* Take action if this is the first board of a new game, or of a
4401        different game than is currently being displayed.  */
4402     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4403         relation == RELATION_ISOLATED_BOARD) {
4404
4405         /* Forget the old game and get the history (if any) of the new one */
4406         if (gameMode != BeginningOfGame) {
4407           Reset(TRUE, TRUE);
4408         }
4409         newGame = TRUE;
4410         if (appData.autoRaiseBoard) BoardToTop();
4411         prevMove = -3;
4412         if (gamenum == -1) {
4413             newGameMode = IcsIdle;
4414         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4415                    appData.getMoveList && !reqFlag) {
4416             /* Need to get game history */
4417             ics_getting_history = H_REQUESTED;
4418             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4419             SendToICS(str);
4420         }
4421
4422         /* Initially flip the board to have black on the bottom if playing
4423            black or if the ICS flip flag is set, but let the user change
4424            it with the Flip View button. */
4425         flipView = appData.autoFlipView ?
4426           (newGameMode == IcsPlayingBlack) || ics_flip :
4427           appData.flipView;
4428
4429         /* Done with values from previous mode; copy in new ones */
4430         gameMode = newGameMode;
4431         ModeHighlight();
4432         ics_gamenum = gamenum;
4433         if (gamenum == gs_gamenum) {
4434             int klen = strlen(gs_kind);
4435             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4436             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4437             gameInfo.event = StrSave(str);
4438         } else {
4439             gameInfo.event = StrSave("ICS game");
4440         }
4441         gameInfo.site = StrSave(appData.icsHost);
4442         gameInfo.date = PGNDate();
4443         gameInfo.round = StrSave("-");
4444         gameInfo.white = StrSave(white);
4445         gameInfo.black = StrSave(black);
4446         timeControl = basetime * 60 * 1000;
4447         timeControl_2 = 0;
4448         timeIncrement = increment * 1000;
4449         movesPerSession = 0;
4450         gameInfo.timeControl = TimeControlTagValue();
4451         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4452   if (appData.debugMode) {
4453     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4454     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4455     setbuf(debugFP, NULL);
4456   }
4457
4458         gameInfo.outOfBook = NULL;
4459
4460         /* Do we have the ratings? */
4461         if (strcmp(player1Name, white) == 0 &&
4462             strcmp(player2Name, black) == 0) {
4463             if (appData.debugMode)
4464               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4465                       player1Rating, player2Rating);
4466             gameInfo.whiteRating = player1Rating;
4467             gameInfo.blackRating = player2Rating;
4468         } else if (strcmp(player2Name, white) == 0 &&
4469                    strcmp(player1Name, black) == 0) {
4470             if (appData.debugMode)
4471               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4472                       player2Rating, player1Rating);
4473             gameInfo.whiteRating = player2Rating;
4474             gameInfo.blackRating = player1Rating;
4475         }
4476         player1Name[0] = player2Name[0] = NULLCHAR;
4477
4478         /* Silence shouts if requested */
4479         if (appData.quietPlay &&
4480             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4481             SendToICS(ics_prefix);
4482             SendToICS("set shout 0\n");
4483         }
4484     }
4485
4486     /* Deal with midgame name changes */
4487     if (!newGame) {
4488         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4489             if (gameInfo.white) free(gameInfo.white);
4490             gameInfo.white = StrSave(white);
4491         }
4492         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4493             if (gameInfo.black) free(gameInfo.black);
4494             gameInfo.black = StrSave(black);
4495         }
4496     }
4497
4498     /* Throw away game result if anything actually changes in examine mode */
4499     if (gameMode == IcsExamining && !newGame) {
4500         gameInfo.result = GameUnfinished;
4501         if (gameInfo.resultDetails != NULL) {
4502             free(gameInfo.resultDetails);
4503             gameInfo.resultDetails = NULL;
4504         }
4505     }
4506
4507     /* In pausing && IcsExamining mode, we ignore boards coming
4508        in if they are in a different variation than we are. */
4509     if (pauseExamInvalid) return;
4510     if (pausing && gameMode == IcsExamining) {
4511         if (moveNum <= pauseExamForwardMostMove) {
4512             pauseExamInvalid = TRUE;
4513             forwardMostMove = pauseExamForwardMostMove;
4514             return;
4515         }
4516     }
4517
4518   if (appData.debugMode) {
4519     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4520   }
4521     /* Parse the board */
4522     for (k = 0; k < ranks; k++) {
4523       for (j = 0; j < files; j++)
4524         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4525       if(gameInfo.holdingsWidth > 1) {
4526            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4527            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4528       }
4529     }
4530     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4531       board[5][BOARD_RGHT+1] = WhiteAngel;
4532       board[6][BOARD_RGHT+1] = WhiteMarshall;
4533       board[1][0] = BlackMarshall;
4534       board[2][0] = BlackAngel;
4535       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4536     }
4537     CopyBoard(boards[moveNum], board);
4538     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4539     if (moveNum == 0) {
4540         startedFromSetupPosition =
4541           !CompareBoards(board, initialPosition);
4542         if(startedFromSetupPosition)
4543             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4544     }
4545
4546     /* [HGM] Set castling rights. Take the outermost Rooks,
4547        to make it also work for FRC opening positions. Note that board12
4548        is really defective for later FRC positions, as it has no way to
4549        indicate which Rook can castle if they are on the same side of King.
4550        For the initial position we grant rights to the outermost Rooks,
4551        and remember thos rights, and we then copy them on positions
4552        later in an FRC game. This means WB might not recognize castlings with
4553        Rooks that have moved back to their original position as illegal,
4554        but in ICS mode that is not its job anyway.
4555     */
4556     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4557     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4558
4559         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4560             if(board[0][i] == WhiteRook) j = i;
4561         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4562         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4563             if(board[0][i] == WhiteRook) j = i;
4564         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4565         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4566             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4567         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4568         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4569             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4570         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4571
4572         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4573         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4574         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4575             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4576         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4577             if(board[BOARD_HEIGHT-1][k] == bKing)
4578                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4579         if(gameInfo.variant == VariantTwoKings) {
4580             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4581             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4582             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4583         }
4584     } else { int r;
4585         r = boards[moveNum][CASTLING][0] = initialRights[0];
4586         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4587         r = boards[moveNum][CASTLING][1] = initialRights[1];
4588         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4589         r = boards[moveNum][CASTLING][3] = initialRights[3];
4590         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4591         r = boards[moveNum][CASTLING][4] = initialRights[4];
4592         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4593         /* wildcastle kludge: always assume King has rights */
4594         r = boards[moveNum][CASTLING][2] = initialRights[2];
4595         r = boards[moveNum][CASTLING][5] = initialRights[5];
4596     }
4597     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4598     boards[moveNum][EP_STATUS] = EP_NONE;
4599     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4600     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4601     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4602
4603
4604     if (ics_getting_history == H_GOT_REQ_HEADER ||
4605         ics_getting_history == H_GOT_UNREQ_HEADER) {
4606         /* This was an initial position from a move list, not
4607            the current position */
4608         return;
4609     }
4610
4611     /* Update currentMove and known move number limits */
4612     newMove = newGame || moveNum > forwardMostMove;
4613
4614     if (newGame) {
4615         forwardMostMove = backwardMostMove = currentMove = moveNum;
4616         if (gameMode == IcsExamining && moveNum == 0) {
4617           /* Workaround for ICS limitation: we are not told the wild
4618              type when starting to examine a game.  But if we ask for
4619              the move list, the move list header will tell us */
4620             ics_getting_history = H_REQUESTED;
4621             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4622             SendToICS(str);
4623         }
4624     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4625                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4626 #if ZIPPY
4627         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4628         /* [HGM] applied this also to an engine that is silently watching        */
4629         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4630             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4631             gameInfo.variant == currentlyInitializedVariant) {
4632           takeback = forwardMostMove - moveNum;
4633           for (i = 0; i < takeback; i++) {
4634             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4635             SendToProgram("undo\n", &first);
4636           }
4637         }
4638 #endif
4639
4640         forwardMostMove = moveNum;
4641         if (!pausing || currentMove > forwardMostMove)
4642           currentMove = forwardMostMove;
4643     } else {
4644         /* New part of history that is not contiguous with old part */
4645         if (pausing && gameMode == IcsExamining) {
4646             pauseExamInvalid = TRUE;
4647             forwardMostMove = pauseExamForwardMostMove;
4648             return;
4649         }
4650         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4651 #if ZIPPY
4652             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4653                 // [HGM] when we will receive the move list we now request, it will be
4654                 // fed to the engine from the first move on. So if the engine is not
4655                 // in the initial position now, bring it there.
4656                 InitChessProgram(&first, 0);
4657             }
4658 #endif
4659             ics_getting_history = H_REQUESTED;
4660             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4661             SendToICS(str);
4662         }
4663         forwardMostMove = backwardMostMove = currentMove = moveNum;
4664     }
4665
4666     /* Update the clocks */
4667     if (strchr(elapsed_time, '.')) {
4668       /* Time is in ms */
4669       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4670       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4671     } else {
4672       /* Time is in seconds */
4673       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4674       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4675     }
4676
4677
4678 #if ZIPPY
4679     if (appData.zippyPlay && newGame &&
4680         gameMode != IcsObserving && gameMode != IcsIdle &&
4681         gameMode != IcsExamining)
4682       ZippyFirstBoard(moveNum, basetime, increment);
4683 #endif
4684
4685     /* Put the move on the move list, first converting
4686        to canonical algebraic form. */
4687     if (moveNum > 0) {
4688   if (appData.debugMode) {
4689     int f = forwardMostMove;
4690     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4691             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4692             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4693     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4694     fprintf(debugFP, "moveNum = %d\n", moveNum);
4695     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4696     setbuf(debugFP, NULL);
4697   }
4698         if (moveNum <= backwardMostMove) {
4699             /* We don't know what the board looked like before
4700                this move.  Punt. */
4701           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4702             strcat(parseList[moveNum - 1], " ");
4703             strcat(parseList[moveNum - 1], elapsed_time);
4704             moveList[moveNum - 1][0] = NULLCHAR;
4705         } else if (strcmp(move_str, "none") == 0) {
4706             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4707             /* Again, we don't know what the board looked like;
4708                this is really the start of the game. */
4709             parseList[moveNum - 1][0] = NULLCHAR;
4710             moveList[moveNum - 1][0] = NULLCHAR;
4711             backwardMostMove = moveNum;
4712             startedFromSetupPosition = TRUE;
4713             fromX = fromY = toX = toY = -1;
4714         } else {
4715           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4716           //                 So we parse the long-algebraic move string in stead of the SAN move
4717           int valid; char buf[MSG_SIZ], *prom;
4718
4719           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4720                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4721           // str looks something like "Q/a1-a2"; kill the slash
4722           if(str[1] == '/')
4723             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4724           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4725           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4726                 strcat(buf, prom); // long move lacks promo specification!
4727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4728                 if(appData.debugMode)
4729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4730                 safeStrCpy(move_str, buf, MSG_SIZ);
4731           }
4732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4733                                 &fromX, &fromY, &toX, &toY, &promoChar)
4734                || ParseOneMove(buf, moveNum - 1, &moveType,
4735                                 &fromX, &fromY, &toX, &toY, &promoChar);
4736           // end of long SAN patch
4737           if (valid) {
4738             (void) CoordsToAlgebraic(boards[moveNum - 1],
4739                                      PosFlags(moveNum - 1),
4740                                      fromY, fromX, toY, toX, promoChar,
4741                                      parseList[moveNum-1]);
4742             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4743               case MT_NONE:
4744               case MT_STALEMATE:
4745               default:
4746                 break;
4747               case MT_CHECK:
4748                 if(gameInfo.variant != VariantShogi)
4749                     strcat(parseList[moveNum - 1], "+");
4750                 break;
4751               case MT_CHECKMATE:
4752               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4753                 strcat(parseList[moveNum - 1], "#");
4754                 break;
4755             }
4756             strcat(parseList[moveNum - 1], " ");
4757             strcat(parseList[moveNum - 1], elapsed_time);
4758             /* currentMoveString is set as a side-effect of ParseOneMove */
4759             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4760             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4761             strcat(moveList[moveNum - 1], "\n");
4762
4763             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4764                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4765               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4766                 ChessSquare old, new = boards[moveNum][k][j];
4767                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4768                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4769                   if(old == new) continue;
4770                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4771                   else if(new == WhiteWazir || new == BlackWazir) {
4772                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4773                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4774                       else boards[moveNum][k][j] = old; // preserve type of Gold
4775                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4776                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4777               }
4778           } else {
4779             /* Move from ICS was illegal!?  Punt. */
4780             if (appData.debugMode) {
4781               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4782               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4783             }
4784             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4785             strcat(parseList[moveNum - 1], " ");
4786             strcat(parseList[moveNum - 1], elapsed_time);
4787             moveList[moveNum - 1][0] = NULLCHAR;
4788             fromX = fromY = toX = toY = -1;
4789           }
4790         }
4791   if (appData.debugMode) {
4792     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4793     setbuf(debugFP, NULL);
4794   }
4795
4796 #if ZIPPY
4797         /* Send move to chess program (BEFORE animating it). */
4798         if (appData.zippyPlay && !newGame && newMove &&
4799            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4800
4801             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4802                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4803                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4804                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4805                             move_str);
4806                     DisplayError(str, 0);
4807                 } else {
4808                     if (first.sendTime) {
4809                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4810                     }
4811                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4812                     if (firstMove && !bookHit) {
4813                         firstMove = FALSE;
4814                         if (first.useColors) {
4815                           SendToProgram(gameMode == IcsPlayingWhite ?
4816                                         "white\ngo\n" :
4817                                         "black\ngo\n", &first);
4818                         } else {
4819                           SendToProgram("go\n", &first);
4820                         }
4821                         first.maybeThinking = TRUE;
4822                     }
4823                 }
4824             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4825               if (moveList[moveNum - 1][0] == NULLCHAR) {
4826                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4827                 DisplayError(str, 0);
4828               } else {
4829                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4830                 SendMoveToProgram(moveNum - 1, &first);
4831               }
4832             }
4833         }
4834 #endif
4835     }
4836
4837     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4838         /* If move comes from a remote source, animate it.  If it
4839            isn't remote, it will have already been animated. */
4840         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4841             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4842         }
4843         if (!pausing && appData.highlightLastMove) {
4844             SetHighlights(fromX, fromY, toX, toY);
4845         }
4846     }
4847
4848     /* Start the clocks */
4849     whiteFlag = blackFlag = FALSE;
4850     appData.clockMode = !(basetime == 0 && increment == 0);
4851     if (ticking == 0) {
4852       ics_clock_paused = TRUE;
4853       StopClocks();
4854     } else if (ticking == 1) {
4855       ics_clock_paused = FALSE;
4856     }
4857     if (gameMode == IcsIdle ||
4858         relation == RELATION_OBSERVING_STATIC ||
4859         relation == RELATION_EXAMINING ||
4860         ics_clock_paused)
4861       DisplayBothClocks();
4862     else
4863       StartClocks();
4864
4865     /* Display opponents and material strengths */
4866     if (gameInfo.variant != VariantBughouse &&
4867         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4868         if (tinyLayout || smallLayout) {
4869             if(gameInfo.variant == VariantNormal)
4870               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4871                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4872                     basetime, increment);
4873             else
4874               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4875                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4876                     basetime, increment, (int) gameInfo.variant);
4877         } else {
4878             if(gameInfo.variant == VariantNormal)
4879               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4880                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4881                     basetime, increment);
4882             else
4883               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4884                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4885                     basetime, increment, VariantName(gameInfo.variant));
4886         }
4887         DisplayTitle(str);
4888   if (appData.debugMode) {
4889     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4890   }
4891     }
4892
4893
4894     /* Display the board */
4895     if (!pausing && !appData.noGUI) {
4896
4897       if (appData.premove)
4898           if (!gotPremove ||
4899              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4900              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4901               ClearPremoveHighlights();
4902
4903       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4904         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4905       DrawPosition(j, boards[currentMove]);
4906
4907       DisplayMove(moveNum - 1);
4908       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4909             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4910               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4911         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4912       }
4913     }
4914
4915     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4916 #if ZIPPY
4917     if(bookHit) { // [HGM] book: simulate book reply
4918         static char bookMove[MSG_SIZ]; // a bit generous?
4919
4920         programStats.nodes = programStats.depth = programStats.time =
4921         programStats.score = programStats.got_only_move = 0;
4922         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4923
4924         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4925         strcat(bookMove, bookHit);
4926         HandleMachineMove(bookMove, &first);
4927     }
4928 #endif
4929 }
4930
4931 void
4932 GetMoveListEvent ()
4933 {
4934     char buf[MSG_SIZ];
4935     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4936         ics_getting_history = H_REQUESTED;
4937         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4938         SendToICS(buf);
4939     }
4940 }
4941
4942 void
4943 SendToBoth (char *msg)
4944 {   // to make it easy to keep two engines in step in dual analysis
4945     SendToProgram(msg, &first);
4946     if(second.analyzing) SendToProgram(msg, &second);
4947 }
4948
4949 void
4950 AnalysisPeriodicEvent (int force)
4951 {
4952     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4953          && !force) || !appData.periodicUpdates)
4954       return;
4955
4956     /* Send . command to Crafty to collect stats */
4957     SendToBoth(".\n");
4958
4959     /* Don't send another until we get a response (this makes
4960        us stop sending to old Crafty's which don't understand
4961        the "." command (sending illegal cmds resets node count & time,
4962        which looks bad)) */
4963     programStats.ok_to_send = 0;
4964 }
4965
4966 void
4967 ics_update_width (int new_width)
4968 {
4969         ics_printf("set width %d\n", new_width);
4970 }
4971
4972 void
4973 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4974 {
4975     char buf[MSG_SIZ];
4976
4977     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4978         // null move in variant where engine does not understand it (for analysis purposes)
4979         SendBoard(cps, moveNum + 1); // send position after move in stead.
4980         return;
4981     }
4982     if (cps->useUsermove) {
4983       SendToProgram("usermove ", cps);
4984     }
4985     if (cps->useSAN) {
4986       char *space;
4987       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4988         int len = space - parseList[moveNum];
4989         memcpy(buf, parseList[moveNum], len);
4990         buf[len++] = '\n';
4991         buf[len] = NULLCHAR;
4992       } else {
4993         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4994       }
4995       SendToProgram(buf, cps);
4996     } else {
4997       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4998         AlphaRank(moveList[moveNum], 4);
4999         SendToProgram(moveList[moveNum], cps);
5000         AlphaRank(moveList[moveNum], 4); // and back
5001       } else
5002       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5003        * the engine. It would be nice to have a better way to identify castle
5004        * moves here. */
5005       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5006                                                                          && cps->useOOCastle) {
5007         int fromX = moveList[moveNum][0] - AAA;
5008         int fromY = moveList[moveNum][1] - ONE;
5009         int toX = moveList[moveNum][2] - AAA;
5010         int toY = moveList[moveNum][3] - ONE;
5011         if((boards[moveNum][fromY][fromX] == WhiteKing
5012             && boards[moveNum][toY][toX] == WhiteRook)
5013            || (boards[moveNum][fromY][fromX] == BlackKing
5014                && boards[moveNum][toY][toX] == BlackRook)) {
5015           if(toX > fromX) SendToProgram("O-O\n", cps);
5016           else SendToProgram("O-O-O\n", cps);
5017         }
5018         else SendToProgram(moveList[moveNum], cps);
5019       } else
5020       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5021         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5022           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5023           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5024                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5025         } else
5026           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5027                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5028         SendToProgram(buf, cps);
5029       }
5030       else SendToProgram(moveList[moveNum], cps);
5031       /* End of additions by Tord */
5032     }
5033
5034     /* [HGM] setting up the opening has brought engine in force mode! */
5035     /*       Send 'go' if we are in a mode where machine should play. */
5036     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5037         (gameMode == TwoMachinesPlay   ||
5038 #if ZIPPY
5039          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5040 #endif
5041          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5042         SendToProgram("go\n", cps);
5043   if (appData.debugMode) {
5044     fprintf(debugFP, "(extra)\n");
5045   }
5046     }
5047     setboardSpoiledMachineBlack = 0;
5048 }
5049
5050 void
5051 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5052 {
5053     char user_move[MSG_SIZ];
5054     char suffix[4];
5055
5056     if(gameInfo.variant == VariantSChess && promoChar) {
5057         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5058         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5059     } else suffix[0] = NULLCHAR;
5060
5061     switch (moveType) {
5062       default:
5063         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5064                 (int)moveType, fromX, fromY, toX, toY);
5065         DisplayError(user_move + strlen("say "), 0);
5066         break;
5067       case WhiteKingSideCastle:
5068       case BlackKingSideCastle:
5069       case WhiteQueenSideCastleWild:
5070       case BlackQueenSideCastleWild:
5071       /* PUSH Fabien */
5072       case WhiteHSideCastleFR:
5073       case BlackHSideCastleFR:
5074       /* POP Fabien */
5075         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5076         break;
5077       case WhiteQueenSideCastle:
5078       case BlackQueenSideCastle:
5079       case WhiteKingSideCastleWild:
5080       case BlackKingSideCastleWild:
5081       /* PUSH Fabien */
5082       case WhiteASideCastleFR:
5083       case BlackASideCastleFR:
5084       /* POP Fabien */
5085         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5086         break;
5087       case WhiteNonPromotion:
5088       case BlackNonPromotion:
5089         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5090         break;
5091       case WhitePromotion:
5092       case BlackPromotion:
5093         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5094           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5095                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5096                 PieceToChar(WhiteFerz));
5097         else if(gameInfo.variant == VariantGreat)
5098           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5099                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5100                 PieceToChar(WhiteMan));
5101         else
5102           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5103                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5104                 promoChar);
5105         break;
5106       case WhiteDrop:
5107       case BlackDrop:
5108       drop:
5109         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5110                  ToUpper(PieceToChar((ChessSquare) fromX)),
5111                  AAA + toX, ONE + toY);
5112         break;
5113       case IllegalMove:  /* could be a variant we don't quite understand */
5114         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5115       case NormalMove:
5116       case WhiteCapturesEnPassant:
5117       case BlackCapturesEnPassant:
5118         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5119                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5120         break;
5121     }
5122     SendToICS(user_move);
5123     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5124         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5125 }
5126
5127 void
5128 UploadGameEvent ()
5129 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5130     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5131     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5132     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5133       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5134       return;
5135     }
5136     if(gameMode != IcsExamining) { // is this ever not the case?
5137         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5138
5139         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5140           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5141         } else { // on FICS we must first go to general examine mode
5142           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5143         }
5144         if(gameInfo.variant != VariantNormal) {
5145             // try figure out wild number, as xboard names are not always valid on ICS
5146             for(i=1; i<=36; i++) {
5147               snprintf(buf, MSG_SIZ, "wild/%d", i);
5148                 if(StringToVariant(buf) == gameInfo.variant) break;
5149             }
5150             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5151             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5152             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5153         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5154         SendToICS(ics_prefix);
5155         SendToICS(buf);
5156         if(startedFromSetupPosition || backwardMostMove != 0) {
5157           fen = PositionToFEN(backwardMostMove, NULL);
5158           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5159             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5160             SendToICS(buf);
5161           } else { // FICS: everything has to set by separate bsetup commands
5162             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5163             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5164             SendToICS(buf);
5165             if(!WhiteOnMove(backwardMostMove)) {
5166                 SendToICS("bsetup tomove black\n");
5167             }
5168             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5169             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5170             SendToICS(buf);
5171             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5172             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5173             SendToICS(buf);
5174             i = boards[backwardMostMove][EP_STATUS];
5175             if(i >= 0) { // set e.p.
5176               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5177                 SendToICS(buf);
5178             }
5179             bsetup++;
5180           }
5181         }
5182       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5183             SendToICS("bsetup done\n"); // switch to normal examining.
5184     }
5185     for(i = backwardMostMove; i<last; i++) {
5186         char buf[20];
5187         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5188         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5189             int len = strlen(moveList[i]);
5190             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5191             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5192         }
5193         SendToICS(buf);
5194     }
5195     SendToICS(ics_prefix);
5196     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5197 }
5198
5199 void
5200 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5201 {
5202     if (rf == DROP_RANK) {
5203       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5204       sprintf(move, "%c@%c%c\n",
5205                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5206     } else {
5207         if (promoChar == 'x' || promoChar == NULLCHAR) {
5208           sprintf(move, "%c%c%c%c\n",
5209                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5210         } else {
5211             sprintf(move, "%c%c%c%c%c\n",
5212                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5213         }
5214     }
5215 }
5216
5217 void
5218 ProcessICSInitScript (FILE *f)
5219 {
5220     char buf[MSG_SIZ];
5221
5222     while (fgets(buf, MSG_SIZ, f)) {
5223         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5224     }
5225
5226     fclose(f);
5227 }
5228
5229
5230 static int lastX, lastY, selectFlag, dragging;
5231
5232 void
5233 Sweep (int step)
5234 {
5235     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5236     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5237     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5238     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5239     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5240     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5241     do {
5242         promoSweep -= step;
5243         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5244         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5245         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5246         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5247         if(!step) step = -1;
5248     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5249             appData.testLegality && (promoSweep == king ||
5250             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5251     if(toX >= 0) {
5252         int victim = boards[currentMove][toY][toX];
5253         boards[currentMove][toY][toX] = promoSweep;
5254         DrawPosition(FALSE, boards[currentMove]);
5255         boards[currentMove][toY][toX] = victim;
5256     } else
5257     ChangeDragPiece(promoSweep);
5258 }
5259
5260 int
5261 PromoScroll (int x, int y)
5262 {
5263   int step = 0;
5264
5265   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5266   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5267   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5268   if(!step) return FALSE;
5269   lastX = x; lastY = y;
5270   if((promoSweep < BlackPawn) == flipView) step = -step;
5271   if(step > 0) selectFlag = 1;
5272   if(!selectFlag) Sweep(step);
5273   return FALSE;
5274 }
5275
5276 void
5277 NextPiece (int step)
5278 {
5279     ChessSquare piece = boards[currentMove][toY][toX];
5280     do {
5281         pieceSweep -= step;
5282         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5283         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5284         if(!step) step = -1;
5285     } while(PieceToChar(pieceSweep) == '.');
5286     boards[currentMove][toY][toX] = pieceSweep;
5287     DrawPosition(FALSE, boards[currentMove]);
5288     boards[currentMove][toY][toX] = piece;
5289 }
5290 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5291 void
5292 AlphaRank (char *move, int n)
5293 {
5294 //    char *p = move, c; int x, y;
5295
5296     if (appData.debugMode) {
5297         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5298     }
5299
5300     if(move[1]=='*' &&
5301        move[2]>='0' && move[2]<='9' &&
5302        move[3]>='a' && move[3]<='x'    ) {
5303         move[1] = '@';
5304         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5305         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5306     } else
5307     if(move[0]>='0' && move[0]<='9' &&
5308        move[1]>='a' && move[1]<='x' &&
5309        move[2]>='0' && move[2]<='9' &&
5310        move[3]>='a' && move[3]<='x'    ) {
5311         /* input move, Shogi -> normal */
5312         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5313         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
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[1]=='@' &&
5318        move[3]>='0' && move[3]<='9' &&
5319        move[2]>='a' && move[2]<='x'    ) {
5320         move[1] = '*';
5321         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5322         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5323     } else
5324     if(
5325        move[0]>='a' && move[0]<='x' &&
5326        move[3]>='0' && move[3]<='9' &&
5327        move[2]>='a' && move[2]<='x'    ) {
5328          /* output move, normal -> Shogi */
5329         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5330         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5331         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5332         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5333         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5334     }
5335     if (appData.debugMode) {
5336         fprintf(debugFP, "   out = '%s'\n", move);
5337     }
5338 }
5339
5340 char yy_textstr[8000];
5341
5342 /* Parser for moves from gnuchess, ICS, or user typein box */
5343 Boolean
5344 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5345 {
5346     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5347
5348     switch (*moveType) {
5349       case WhitePromotion:
5350       case BlackPromotion:
5351       case WhiteNonPromotion:
5352       case BlackNonPromotion:
5353       case NormalMove:
5354       case WhiteCapturesEnPassant:
5355       case BlackCapturesEnPassant:
5356       case WhiteKingSideCastle:
5357       case WhiteQueenSideCastle:
5358       case BlackKingSideCastle:
5359       case BlackQueenSideCastle:
5360       case WhiteKingSideCastleWild:
5361       case WhiteQueenSideCastleWild:
5362       case BlackKingSideCastleWild:
5363       case BlackQueenSideCastleWild:
5364       /* Code added by Tord: */
5365       case WhiteHSideCastleFR:
5366       case WhiteASideCastleFR:
5367       case BlackHSideCastleFR:
5368       case BlackASideCastleFR:
5369       /* End of code added by Tord */
5370       case IllegalMove:         /* bug or odd chess variant */
5371         *fromX = currentMoveString[0] - AAA;
5372         *fromY = currentMoveString[1] - ONE;
5373         *toX = currentMoveString[2] - AAA;
5374         *toY = currentMoveString[3] - ONE;
5375         *promoChar = currentMoveString[4];
5376         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5377             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5378     if (appData.debugMode) {
5379         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5380     }
5381             *fromX = *fromY = *toX = *toY = 0;
5382             return FALSE;
5383         }
5384         if (appData.testLegality) {
5385           return (*moveType != IllegalMove);
5386         } else {
5387           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5388                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5389         }
5390
5391       case WhiteDrop:
5392       case BlackDrop:
5393         *fromX = *moveType == WhiteDrop ?
5394           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5395           (int) CharToPiece(ToLower(currentMoveString[0]));
5396         *fromY = DROP_RANK;
5397         *toX = currentMoveString[2] - AAA;
5398         *toY = currentMoveString[3] - ONE;
5399         *promoChar = NULLCHAR;
5400         return TRUE;
5401
5402       case AmbiguousMove:
5403       case ImpossibleMove:
5404       case EndOfFile:
5405       case ElapsedTime:
5406       case Comment:
5407       case PGNTag:
5408       case NAG:
5409       case WhiteWins:
5410       case BlackWins:
5411       case GameIsDrawn:
5412       default:
5413     if (appData.debugMode) {
5414         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5415     }
5416         /* bug? */
5417         *fromX = *fromY = *toX = *toY = 0;
5418         *promoChar = NULLCHAR;
5419         return FALSE;
5420     }
5421 }
5422
5423 Boolean pushed = FALSE;
5424 char *lastParseAttempt;
5425
5426 void
5427 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5428 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5429   int fromX, fromY, toX, toY; char promoChar;
5430   ChessMove moveType;
5431   Boolean valid;
5432   int nr = 0;
5433
5434   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5435   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5436     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5437     pushed = TRUE;
5438   }
5439   endPV = forwardMostMove;
5440   do {
5441     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5442     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5443     lastParseAttempt = pv;
5444     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5445     if(!valid && nr == 0 &&
5446        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5447         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5448         // Hande case where played move is different from leading PV move
5449         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5450         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5451         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5452         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5453           endPV += 2; // if position different, keep this
5454           moveList[endPV-1][0] = fromX + AAA;
5455           moveList[endPV-1][1] = fromY + ONE;
5456           moveList[endPV-1][2] = toX + AAA;
5457           moveList[endPV-1][3] = toY + ONE;
5458           parseList[endPV-1][0] = NULLCHAR;
5459           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5460         }
5461       }
5462     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5463     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5464     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5465     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5466         valid++; // allow comments in PV
5467         continue;
5468     }
5469     nr++;
5470     if(endPV+1 > framePtr) break; // no space, truncate
5471     if(!valid) break;
5472     endPV++;
5473     CopyBoard(boards[endPV], boards[endPV-1]);
5474     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5475     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5476     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5477     CoordsToAlgebraic(boards[endPV - 1],
5478                              PosFlags(endPV - 1),
5479                              fromY, fromX, toY, toX, promoChar,
5480                              parseList[endPV - 1]);
5481   } while(valid);
5482   if(atEnd == 2) return; // used hidden, for PV conversion
5483   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5484   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5485   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5486                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5487   DrawPosition(TRUE, boards[currentMove]);
5488 }
5489
5490 int
5491 MultiPV (ChessProgramState *cps)
5492 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5493         int i;
5494         for(i=0; i<cps->nrOptions; i++)
5495             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5496                 return i;
5497         return -1;
5498 }
5499
5500 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5501
5502 Boolean
5503 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5504 {
5505         int startPV, multi, lineStart, origIndex = index;
5506         char *p, buf2[MSG_SIZ];
5507         ChessProgramState *cps = (pane ? &second : &first);
5508
5509         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5510         lastX = x; lastY = y;
5511         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5512         lineStart = startPV = index;
5513         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5514         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5515         index = startPV;
5516         do{ while(buf[index] && buf[index] != '\n') index++;
5517         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5518         buf[index] = 0;
5519         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5520                 int n = cps->option[multi].value;
5521                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5522                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5523                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5524                 cps->option[multi].value = n;
5525                 *start = *end = 0;
5526                 return FALSE;
5527         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5528                 ExcludeClick(origIndex - lineStart);
5529                 return FALSE;
5530         }
5531         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5532         *start = startPV; *end = index-1;
5533         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5534         return TRUE;
5535 }
5536
5537 char *
5538 PvToSAN (char *pv)
5539 {
5540         static char buf[10*MSG_SIZ];
5541         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5542         *buf = NULLCHAR;
5543         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5544         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5545         for(i = forwardMostMove; i<endPV; i++){
5546             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5547             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5548             k += strlen(buf+k);
5549         }
5550         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5551         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5552         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5553         endPV = savedEnd;
5554         return buf;
5555 }
5556
5557 Boolean
5558 LoadPV (int x, int y)
5559 { // called on right mouse click to load PV
5560   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5561   lastX = x; lastY = y;
5562   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5563   extendGame = FALSE;
5564   return TRUE;
5565 }
5566
5567 void
5568 UnLoadPV ()
5569 {
5570   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5571   if(endPV < 0) return;
5572   if(appData.autoCopyPV) CopyFENToClipboard();
5573   endPV = -1;
5574   if(extendGame && currentMove > forwardMostMove) {
5575         Boolean saveAnimate = appData.animate;
5576         if(pushed) {
5577             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5578                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5579             } else storedGames--; // abandon shelved tail of original game
5580         }
5581         pushed = FALSE;
5582         forwardMostMove = currentMove;
5583         currentMove = oldFMM;
5584         appData.animate = FALSE;
5585         ToNrEvent(forwardMostMove);
5586         appData.animate = saveAnimate;
5587   }
5588   currentMove = forwardMostMove;
5589   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5590   ClearPremoveHighlights();
5591   DrawPosition(TRUE, boards[currentMove]);
5592 }
5593
5594 void
5595 MovePV (int x, int y, int h)
5596 { // step through PV based on mouse coordinates (called on mouse move)
5597   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5598
5599   // we must somehow check if right button is still down (might be released off board!)
5600   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5601   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5602   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5603   if(!step) return;
5604   lastX = x; lastY = y;
5605
5606   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5607   if(endPV < 0) return;
5608   if(y < margin) step = 1; else
5609   if(y > h - margin) step = -1;
5610   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5611   currentMove += step;
5612   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5613   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5614                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5615   DrawPosition(FALSE, boards[currentMove]);
5616 }
5617
5618
5619 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5620 // All positions will have equal probability, but the current method will not provide a unique
5621 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5622 #define DARK 1
5623 #define LITE 2
5624 #define ANY 3
5625
5626 int squaresLeft[4];
5627 int piecesLeft[(int)BlackPawn];
5628 int seed, nrOfShuffles;
5629
5630 void
5631 GetPositionNumber ()
5632 {       // sets global variable seed
5633         int i;
5634
5635         seed = appData.defaultFrcPosition;
5636         if(seed < 0) { // randomize based on time for negative FRC position numbers
5637                 for(i=0; i<50; i++) seed += random();
5638                 seed = random() ^ random() >> 8 ^ random() << 8;
5639                 if(seed<0) seed = -seed;
5640         }
5641 }
5642
5643 int
5644 put (Board board, int pieceType, int rank, int n, int shade)
5645 // put the piece on the (n-1)-th empty squares of the given shade
5646 {
5647         int i;
5648
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5650                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5651                         board[rank][i] = (ChessSquare) pieceType;
5652                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5653                         squaresLeft[ANY]--;
5654                         piecesLeft[pieceType]--;
5655                         return i;
5656                 }
5657         }
5658         return -1;
5659 }
5660
5661
5662 void
5663 AddOnePiece (Board board, int pieceType, int rank, int shade)
5664 // calculate where the next piece goes, (any empty square), and put it there
5665 {
5666         int i;
5667
5668         i = seed % squaresLeft[shade];
5669         nrOfShuffles *= squaresLeft[shade];
5670         seed /= squaresLeft[shade];
5671         put(board, pieceType, rank, i, shade);
5672 }
5673
5674 void
5675 AddTwoPieces (Board board, int pieceType, int rank)
5676 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5677 {
5678         int i, n=squaresLeft[ANY], j=n-1, k;
5679
5680         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5681         i = seed % k;  // pick one
5682         nrOfShuffles *= k;
5683         seed /= k;
5684         while(i >= j) i -= j--;
5685         j = n - 1 - j; i += j;
5686         put(board, pieceType, rank, j, ANY);
5687         put(board, pieceType, rank, i, ANY);
5688 }
5689
5690 void
5691 SetUpShuffle (Board board, int number)
5692 {
5693         int i, p, first=1;
5694
5695         GetPositionNumber(); nrOfShuffles = 1;
5696
5697         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5698         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5699         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5700
5701         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5702
5703         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5704             p = (int) board[0][i];
5705             if(p < (int) BlackPawn) piecesLeft[p] ++;
5706             board[0][i] = EmptySquare;
5707         }
5708
5709         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5710             // shuffles restricted to allow normal castling put KRR first
5711             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5712                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5713             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5714                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5715             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5716                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5717             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5718                 put(board, WhiteRook, 0, 0, ANY);
5719             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5720         }
5721
5722         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5723             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5724             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5725                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5726                 while(piecesLeft[p] >= 2) {
5727                     AddOnePiece(board, p, 0, LITE);
5728                     AddOnePiece(board, p, 0, DARK);
5729                 }
5730                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5731             }
5732
5733         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5734             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5735             // but we leave King and Rooks for last, to possibly obey FRC restriction
5736             if(p == (int)WhiteRook) continue;
5737             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5738             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5739         }
5740
5741         // now everything is placed, except perhaps King (Unicorn) and Rooks
5742
5743         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5744             // Last King gets castling rights
5745             while(piecesLeft[(int)WhiteUnicorn]) {
5746                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5747                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5748             }
5749
5750             while(piecesLeft[(int)WhiteKing]) {
5751                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5752                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5753             }
5754
5755
5756         } else {
5757             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5758             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5759         }
5760
5761         // Only Rooks can be left; simply place them all
5762         while(piecesLeft[(int)WhiteRook]) {
5763                 i = put(board, WhiteRook, 0, 0, ANY);
5764                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5765                         if(first) {
5766                                 first=0;
5767                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5768                         }
5769                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5770                 }
5771         }
5772         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5773             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5774         }
5775
5776         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5777 }
5778
5779 int
5780 SetCharTable (char *table, const char * map)
5781 /* [HGM] moved here from winboard.c because of its general usefulness */
5782 /*       Basically a safe strcpy that uses the last character as King */
5783 {
5784     int result = FALSE; int NrPieces;
5785
5786     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5787                     && NrPieces >= 12 && !(NrPieces&1)) {
5788         int i; /* [HGM] Accept even length from 12 to 34 */
5789
5790         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5791         for( i=0; i<NrPieces/2-1; i++ ) {
5792             table[i] = map[i];
5793             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5794         }
5795         table[(int) WhiteKing]  = map[NrPieces/2-1];
5796         table[(int) BlackKing]  = map[NrPieces-1];
5797
5798         result = TRUE;
5799     }
5800
5801     return result;
5802 }
5803
5804 void
5805 Prelude (Board board)
5806 {       // [HGM] superchess: random selection of exo-pieces
5807         int i, j, k; ChessSquare p;
5808         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5809
5810         GetPositionNumber(); // use FRC position number
5811
5812         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5813             SetCharTable(pieceToChar, appData.pieceToCharTable);
5814             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5815                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5816         }
5817
5818         j = seed%4;                 seed /= 4;
5819         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5820         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5821         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5822         j = seed%3 + (seed%3 >= j); seed /= 3;
5823         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5824         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5825         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5826         j = seed%3;                 seed /= 3;
5827         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5828         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5829         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5830         j = seed%2 + (seed%2 >= j); seed /= 2;
5831         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5832         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5833         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5834         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5835         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5836         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5837         put(board, exoPieces[0],    0, 0, ANY);
5838         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5839 }
5840
5841 void
5842 InitPosition (int redraw)
5843 {
5844     ChessSquare (* pieces)[BOARD_FILES];
5845     int i, j, pawnRow, overrule,
5846     oldx = gameInfo.boardWidth,
5847     oldy = gameInfo.boardHeight,
5848     oldh = gameInfo.holdingsWidth;
5849     static int oldv;
5850
5851     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5852
5853     /* [AS] Initialize pv info list [HGM] and game status */
5854     {
5855         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5856             pvInfoList[i].depth = 0;
5857             boards[i][EP_STATUS] = EP_NONE;
5858             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5859         }
5860
5861         initialRulePlies = 0; /* 50-move counter start */
5862
5863         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5864         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5865     }
5866
5867
5868     /* [HGM] logic here is completely changed. In stead of full positions */
5869     /* the initialized data only consist of the two backranks. The switch */
5870     /* selects which one we will use, which is than copied to the Board   */
5871     /* initialPosition, which for the rest is initialized by Pawns and    */
5872     /* empty squares. This initial position is then copied to boards[0],  */
5873     /* possibly after shuffling, so that it remains available.            */
5874
5875     gameInfo.holdingsWidth = 0; /* default board sizes */
5876     gameInfo.boardWidth    = 8;
5877     gameInfo.boardHeight   = 8;
5878     gameInfo.holdingsSize  = 0;
5879     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5880     for(i=0; i<BOARD_FILES-2; i++)
5881       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5882     initialPosition[EP_STATUS] = EP_NONE;
5883     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5884     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5885          SetCharTable(pieceNickName, appData.pieceNickNames);
5886     else SetCharTable(pieceNickName, "............");
5887     pieces = FIDEArray;
5888
5889     switch (gameInfo.variant) {
5890     case VariantFischeRandom:
5891       shuffleOpenings = TRUE;
5892     default:
5893       break;
5894     case VariantShatranj:
5895       pieces = ShatranjArray;
5896       nrCastlingRights = 0;
5897       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5898       break;
5899     case VariantMakruk:
5900       pieces = makrukArray;
5901       nrCastlingRights = 0;
5902       startedFromSetupPosition = TRUE;
5903       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5904       break;
5905     case VariantTwoKings:
5906       pieces = twoKingsArray;
5907       break;
5908     case VariantGrand:
5909       pieces = GrandArray;
5910       nrCastlingRights = 0;
5911       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5912       gameInfo.boardWidth = 10;
5913       gameInfo.boardHeight = 10;
5914       gameInfo.holdingsSize = 7;
5915       break;
5916     case VariantCapaRandom:
5917       shuffleOpenings = TRUE;
5918     case VariantCapablanca:
5919       pieces = CapablancaArray;
5920       gameInfo.boardWidth = 10;
5921       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5922       break;
5923     case VariantGothic:
5924       pieces = GothicArray;
5925       gameInfo.boardWidth = 10;
5926       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5927       break;
5928     case VariantSChess:
5929       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5930       gameInfo.holdingsSize = 7;
5931       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5932       break;
5933     case VariantJanus:
5934       pieces = JanusArray;
5935       gameInfo.boardWidth = 10;
5936       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5937       nrCastlingRights = 6;
5938         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5939         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5940         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5941         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5942         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5943         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5944       break;
5945     case VariantFalcon:
5946       pieces = FalconArray;
5947       gameInfo.boardWidth = 10;
5948       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5949       break;
5950     case VariantXiangqi:
5951       pieces = XiangqiArray;
5952       gameInfo.boardWidth  = 9;
5953       gameInfo.boardHeight = 10;
5954       nrCastlingRights = 0;
5955       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5956       break;
5957     case VariantShogi:
5958       pieces = ShogiArray;
5959       gameInfo.boardWidth  = 9;
5960       gameInfo.boardHeight = 9;
5961       gameInfo.holdingsSize = 7;
5962       nrCastlingRights = 0;
5963       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5964       break;
5965     case VariantCourier:
5966       pieces = CourierArray;
5967       gameInfo.boardWidth  = 12;
5968       nrCastlingRights = 0;
5969       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5970       break;
5971     case VariantKnightmate:
5972       pieces = KnightmateArray;
5973       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5974       break;
5975     case VariantSpartan:
5976       pieces = SpartanArray;
5977       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5978       break;
5979     case VariantFairy:
5980       pieces = fairyArray;
5981       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5982       break;
5983     case VariantGreat:
5984       pieces = GreatArray;
5985       gameInfo.boardWidth = 10;
5986       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5987       gameInfo.holdingsSize = 8;
5988       break;
5989     case VariantSuper:
5990       pieces = FIDEArray;
5991       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5992       gameInfo.holdingsSize = 8;
5993       startedFromSetupPosition = TRUE;
5994       break;
5995     case VariantCrazyhouse:
5996     case VariantBughouse:
5997       pieces = FIDEArray;
5998       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5999       gameInfo.holdingsSize = 5;
6000       break;
6001     case VariantWildCastle:
6002       pieces = FIDEArray;
6003       /* !!?shuffle with kings guaranteed to be on d or e file */
6004       shuffleOpenings = 1;
6005       break;
6006     case VariantNoCastle:
6007       pieces = FIDEArray;
6008       nrCastlingRights = 0;
6009       /* !!?unconstrained back-rank shuffle */
6010       shuffleOpenings = 1;
6011       break;
6012     }
6013
6014     overrule = 0;
6015     if(appData.NrFiles >= 0) {
6016         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6017         gameInfo.boardWidth = appData.NrFiles;
6018     }
6019     if(appData.NrRanks >= 0) {
6020         gameInfo.boardHeight = appData.NrRanks;
6021     }
6022     if(appData.holdingsSize >= 0) {
6023         i = appData.holdingsSize;
6024         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6025         gameInfo.holdingsSize = i;
6026     }
6027     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6028     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6029         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6030
6031     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6032     if(pawnRow < 1) pawnRow = 1;
6033     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
6034
6035     /* User pieceToChar list overrules defaults */
6036     if(appData.pieceToCharTable != NULL)
6037         SetCharTable(pieceToChar, appData.pieceToCharTable);
6038
6039     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6040
6041         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6042             s = (ChessSquare) 0; /* account holding counts in guard band */
6043         for( i=0; i<BOARD_HEIGHT; i++ )
6044             initialPosition[i][j] = s;
6045
6046         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6047         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6048         initialPosition[pawnRow][j] = WhitePawn;
6049         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6050         if(gameInfo.variant == VariantXiangqi) {
6051             if(j&1) {
6052                 initialPosition[pawnRow][j] =
6053                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6054                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6055                    initialPosition[2][j] = WhiteCannon;
6056                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6057                 }
6058             }
6059         }
6060         if(gameInfo.variant == VariantGrand) {
6061             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6062                initialPosition[0][j] = WhiteRook;
6063                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6064             }
6065         }
6066         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6067     }
6068     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6069
6070             j=BOARD_LEFT+1;
6071             initialPosition[1][j] = WhiteBishop;
6072             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6073             j=BOARD_RGHT-2;
6074             initialPosition[1][j] = WhiteRook;
6075             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6076     }
6077
6078     if( nrCastlingRights == -1) {
6079         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6080         /*       This sets default castling rights from none to normal corners   */
6081         /* Variants with other castling rights must set them themselves above    */
6082         nrCastlingRights = 6;
6083
6084         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6085         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6086         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6087         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6088         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6089         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6090      }
6091
6092      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6093      if(gameInfo.variant == VariantGreat) { // promotion commoners
6094         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6095         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6096         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6097         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6098      }
6099      if( gameInfo.variant == VariantSChess ) {
6100       initialPosition[1][0] = BlackMarshall;
6101       initialPosition[2][0] = BlackAngel;
6102       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6103       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6104       initialPosition[1][1] = initialPosition[2][1] =
6105       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6106      }
6107   if (appData.debugMode) {
6108     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6109   }
6110     if(shuffleOpenings) {
6111         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6112         startedFromSetupPosition = TRUE;
6113     }
6114     if(startedFromPositionFile) {
6115       /* [HGM] loadPos: use PositionFile for every new game */
6116       CopyBoard(initialPosition, filePosition);
6117       for(i=0; i<nrCastlingRights; i++)
6118           initialRights[i] = filePosition[CASTLING][i];
6119       startedFromSetupPosition = TRUE;
6120     }
6121
6122     CopyBoard(boards[0], initialPosition);
6123
6124     if(oldx != gameInfo.boardWidth ||
6125        oldy != gameInfo.boardHeight ||
6126        oldv != gameInfo.variant ||
6127        oldh != gameInfo.holdingsWidth
6128                                          )
6129             InitDrawingSizes(-2 ,0);
6130
6131     oldv = gameInfo.variant;
6132     if (redraw)
6133       DrawPosition(TRUE, boards[currentMove]);
6134 }
6135
6136 void
6137 SendBoard (ChessProgramState *cps, int moveNum)
6138 {
6139     char message[MSG_SIZ];
6140
6141     if (cps->useSetboard) {
6142       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6143       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6144       SendToProgram(message, cps);
6145       free(fen);
6146
6147     } else {
6148       ChessSquare *bp;
6149       int i, j, left=0, right=BOARD_WIDTH;
6150       /* Kludge to set black to move, avoiding the troublesome and now
6151        * deprecated "black" command.
6152        */
6153       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6154         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6155
6156       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6157
6158       SendToProgram("edit\n", cps);
6159       SendToProgram("#\n", cps);
6160       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6161         bp = &boards[moveNum][i][left];
6162         for (j = left; j < right; j++, bp++) {
6163           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6164           if ((int) *bp < (int) BlackPawn) {
6165             if(j == BOARD_RGHT+1)
6166                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6167             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6168             if(message[0] == '+' || message[0] == '~') {
6169               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6170                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6171                         AAA + j, ONE + i);
6172             }
6173             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6174                 message[1] = BOARD_RGHT   - 1 - j + '1';
6175                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6176             }
6177             SendToProgram(message, cps);
6178           }
6179         }
6180       }
6181
6182       SendToProgram("c\n", cps);
6183       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6184         bp = &boards[moveNum][i][left];
6185         for (j = left; j < right; j++, bp++) {
6186           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6187           if (((int) *bp != (int) EmptySquare)
6188               && ((int) *bp >= (int) BlackPawn)) {
6189             if(j == BOARD_LEFT-2)
6190                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6191             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6192                     AAA + j, ONE + i);
6193             if(message[0] == '+' || message[0] == '~') {
6194               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6195                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6196                         AAA + j, ONE + i);
6197             }
6198             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6199                 message[1] = BOARD_RGHT   - 1 - j + '1';
6200                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6201             }
6202             SendToProgram(message, cps);
6203           }
6204         }
6205       }
6206
6207       SendToProgram(".\n", cps);
6208     }
6209     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6210 }
6211
6212 char exclusionHeader[MSG_SIZ];
6213 int exCnt, excludePtr;
6214 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6215 static Exclusion excluTab[200];
6216 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6217
6218 static void
6219 WriteMap (int s)
6220 {
6221     int j;
6222     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6223     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6224 }
6225
6226 static void
6227 ClearMap ()
6228 {
6229     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6230     excludePtr = 24; exCnt = 0;
6231     WriteMap(0);
6232 }
6233
6234 static void
6235 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6236 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6237     char buf[2*MOVE_LEN], *p;
6238     Exclusion *e = excluTab;
6239     int i;
6240     for(i=0; i<exCnt; i++)
6241         if(e[i].ff == fromX && e[i].fr == fromY &&
6242            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6243     if(i == exCnt) { // was not in exclude list; add it
6244         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6245         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6246             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6247             return; // abort
6248         }
6249         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6250         excludePtr++; e[i].mark = excludePtr++;
6251         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6252         exCnt++;
6253     }
6254     exclusionHeader[e[i].mark] = state;
6255 }
6256
6257 static int
6258 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6259 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6260     char buf[MSG_SIZ];
6261     int j, k;
6262     ChessMove moveType;
6263     if((signed char)promoChar == -1) { // kludge to indicate best move
6264         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6265             return 1; // if unparsable, abort
6266     }
6267     // update exclusion map (resolving toggle by consulting existing state)
6268     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6269     j = k%8; k >>= 3;
6270     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6271     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6272          excludeMap[k] |=   1<<j;
6273     else excludeMap[k] &= ~(1<<j);
6274     // update header
6275     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6276     // inform engine
6277     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6278     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6279     SendToBoth(buf);
6280     return (state == '+');
6281 }
6282
6283 static void
6284 ExcludeClick (int index)
6285 {
6286     int i, j;
6287     Exclusion *e = excluTab;
6288     if(index < 25) { // none, best or tail clicked
6289         if(index < 13) { // none: include all
6290             WriteMap(0); // clear map
6291             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6292             SendToBoth("include all\n"); // and inform engine
6293         } else if(index > 18) { // tail
6294             if(exclusionHeader[19] == '-') { // tail was excluded
6295                 SendToBoth("include all\n");
6296                 WriteMap(0); // clear map completely
6297                 // now re-exclude selected moves
6298                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6299                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6300             } else { // tail was included or in mixed state
6301                 SendToBoth("exclude all\n");
6302                 WriteMap(0xFF); // fill map completely
6303                 // now re-include selected moves
6304                 j = 0; // count them
6305                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6306                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6307                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6308             }
6309         } else { // best
6310             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6311         }
6312     } else {
6313         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6314             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6315             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6316             break;
6317         }
6318     }
6319 }
6320
6321 ChessSquare
6322 DefaultPromoChoice (int white)
6323 {
6324     ChessSquare result;
6325     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6326         result = WhiteFerz; // no choice
6327     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6328         result= WhiteKing; // in Suicide Q is the last thing we want
6329     else if(gameInfo.variant == VariantSpartan)
6330         result = white ? WhiteQueen : WhiteAngel;
6331     else result = WhiteQueen;
6332     if(!white) result = WHITE_TO_BLACK result;
6333     return result;
6334 }
6335
6336 static int autoQueen; // [HGM] oneclick
6337
6338 int
6339 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6340 {
6341     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6342     /* [HGM] add Shogi promotions */
6343     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6344     ChessSquare piece;
6345     ChessMove moveType;
6346     Boolean premove;
6347
6348     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6349     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6350
6351     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6352       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6353         return FALSE;
6354
6355     piece = boards[currentMove][fromY][fromX];
6356     if(gameInfo.variant == VariantShogi) {
6357         promotionZoneSize = BOARD_HEIGHT/3;
6358         highestPromotingPiece = (int)WhiteFerz;
6359     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6360         promotionZoneSize = 3;
6361     }
6362
6363     // Treat Lance as Pawn when it is not representing Amazon
6364     if(gameInfo.variant != VariantSuper) {
6365         if(piece == WhiteLance) piece = WhitePawn; else
6366         if(piece == BlackLance) piece = BlackPawn;
6367     }
6368
6369     // next weed out all moves that do not touch the promotion zone at all
6370     if((int)piece >= BlackPawn) {
6371         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6372              return FALSE;
6373         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6374     } else {
6375         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6376            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6377     }
6378
6379     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6380
6381     // weed out mandatory Shogi promotions
6382     if(gameInfo.variant == VariantShogi) {
6383         if(piece >= BlackPawn) {
6384             if(toY == 0 && piece == BlackPawn ||
6385                toY == 0 && piece == BlackQueen ||
6386                toY <= 1 && piece == BlackKnight) {
6387                 *promoChoice = '+';
6388                 return FALSE;
6389             }
6390         } else {
6391             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6392                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6393                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6394                 *promoChoice = '+';
6395                 return FALSE;
6396             }
6397         }
6398     }
6399
6400     // weed out obviously illegal Pawn moves
6401     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6402         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6403         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6404         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6405         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6406         // note we are not allowed to test for valid (non-)capture, due to premove
6407     }
6408
6409     // we either have a choice what to promote to, or (in Shogi) whether to promote
6410     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6411         *promoChoice = PieceToChar(BlackFerz);  // no choice
6412         return FALSE;
6413     }
6414     // no sense asking what we must promote to if it is going to explode...
6415     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6416         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6417         return FALSE;
6418     }
6419     // give caller the default choice even if we will not make it
6420     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6421     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6422     if(        sweepSelect && gameInfo.variant != VariantGreat
6423                            && gameInfo.variant != VariantGrand
6424                            && gameInfo.variant != VariantSuper) return FALSE;
6425     if(autoQueen) return FALSE; // predetermined
6426
6427     // suppress promotion popup on illegal moves that are not premoves
6428     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6429               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6430     if(appData.testLegality && !premove) {
6431         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6432                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6433         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6434             return FALSE;
6435     }
6436
6437     return TRUE;
6438 }
6439
6440 int
6441 InPalace (int row, int column)
6442 {   /* [HGM] for Xiangqi */
6443     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6444          column < (BOARD_WIDTH + 4)/2 &&
6445          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6446     return FALSE;
6447 }
6448
6449 int
6450 PieceForSquare (int x, int y)
6451 {
6452   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6453      return -1;
6454   else
6455      return boards[currentMove][y][x];
6456 }
6457
6458 int
6459 OKToStartUserMove (int x, int y)
6460 {
6461     ChessSquare from_piece;
6462     int white_piece;
6463
6464     if (matchMode) return FALSE;
6465     if (gameMode == EditPosition) return TRUE;
6466
6467     if (x >= 0 && y >= 0)
6468       from_piece = boards[currentMove][y][x];
6469     else
6470       from_piece = EmptySquare;
6471
6472     if (from_piece == EmptySquare) return FALSE;
6473
6474     white_piece = (int)from_piece >= (int)WhitePawn &&
6475       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6476
6477     switch (gameMode) {
6478       case AnalyzeFile:
6479       case TwoMachinesPlay:
6480       case EndOfGame:
6481         return FALSE;
6482
6483       case IcsObserving:
6484       case IcsIdle:
6485         return FALSE;
6486
6487       case MachinePlaysWhite:
6488       case IcsPlayingBlack:
6489         if (appData.zippyPlay) return FALSE;
6490         if (white_piece) {
6491             DisplayMoveError(_("You are playing Black"));
6492             return FALSE;
6493         }
6494         break;
6495
6496       case MachinePlaysBlack:
6497       case IcsPlayingWhite:
6498         if (appData.zippyPlay) return FALSE;
6499         if (!white_piece) {
6500             DisplayMoveError(_("You are playing White"));
6501             return FALSE;
6502         }
6503         break;
6504
6505       case PlayFromGameFile:
6506             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6507       case EditGame:
6508         if (!white_piece && WhiteOnMove(currentMove)) {
6509             DisplayMoveError(_("It is White's turn"));
6510             return FALSE;
6511         }
6512         if (white_piece && !WhiteOnMove(currentMove)) {
6513             DisplayMoveError(_("It is Black's turn"));
6514             return FALSE;
6515         }
6516         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6517             /* Editing correspondence game history */
6518             /* Could disallow this or prompt for confirmation */
6519             cmailOldMove = -1;
6520         }
6521         break;
6522
6523       case BeginningOfGame:
6524         if (appData.icsActive) return FALSE;
6525         if (!appData.noChessProgram) {
6526             if (!white_piece) {
6527                 DisplayMoveError(_("You are playing White"));
6528                 return FALSE;
6529             }
6530         }
6531         break;
6532
6533       case Training:
6534         if (!white_piece && WhiteOnMove(currentMove)) {
6535             DisplayMoveError(_("It is White's turn"));
6536             return FALSE;
6537         }
6538         if (white_piece && !WhiteOnMove(currentMove)) {
6539             DisplayMoveError(_("It is Black's turn"));
6540             return FALSE;
6541         }
6542         break;
6543
6544       default:
6545       case IcsExamining:
6546         break;
6547     }
6548     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6549         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6550         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6551         && gameMode != AnalyzeFile && gameMode != Training) {
6552         DisplayMoveError(_("Displayed position is not current"));
6553         return FALSE;
6554     }
6555     return TRUE;
6556 }
6557
6558 Boolean
6559 OnlyMove (int *x, int *y, Boolean captures)
6560 {
6561     DisambiguateClosure cl;
6562     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6563     switch(gameMode) {
6564       case MachinePlaysBlack:
6565       case IcsPlayingWhite:
6566       case BeginningOfGame:
6567         if(!WhiteOnMove(currentMove)) return FALSE;
6568         break;
6569       case MachinePlaysWhite:
6570       case IcsPlayingBlack:
6571         if(WhiteOnMove(currentMove)) return FALSE;
6572         break;
6573       case EditGame:
6574         break;
6575       default:
6576         return FALSE;
6577     }
6578     cl.pieceIn = EmptySquare;
6579     cl.rfIn = *y;
6580     cl.ffIn = *x;
6581     cl.rtIn = -1;
6582     cl.ftIn = -1;
6583     cl.promoCharIn = NULLCHAR;
6584     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6585     if( cl.kind == NormalMove ||
6586         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6587         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6588         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6589       fromX = cl.ff;
6590       fromY = cl.rf;
6591       *x = cl.ft;
6592       *y = cl.rt;
6593       return TRUE;
6594     }
6595     if(cl.kind != ImpossibleMove) return FALSE;
6596     cl.pieceIn = EmptySquare;
6597     cl.rfIn = -1;
6598     cl.ffIn = -1;
6599     cl.rtIn = *y;
6600     cl.ftIn = *x;
6601     cl.promoCharIn = NULLCHAR;
6602     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6603     if( cl.kind == NormalMove ||
6604         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6605         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6606         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6607       fromX = cl.ff;
6608       fromY = cl.rf;
6609       *x = cl.ft;
6610       *y = cl.rt;
6611       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6612       return TRUE;
6613     }
6614     return FALSE;
6615 }
6616
6617 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6618 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6619 int lastLoadGameUseList = FALSE;
6620 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6621 ChessMove lastLoadGameStart = EndOfFile;
6622 int doubleClick;
6623
6624 void
6625 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6626 {
6627     ChessMove moveType;
6628     ChessSquare pup;
6629     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6630
6631     /* Check if the user is playing in turn.  This is complicated because we
6632        let the user "pick up" a piece before it is his turn.  So the piece he
6633        tried to pick up may have been captured by the time he puts it down!
6634        Therefore we use the color the user is supposed to be playing in this
6635        test, not the color of the piece that is currently on the starting
6636        square---except in EditGame mode, where the user is playing both
6637        sides; fortunately there the capture race can't happen.  (It can
6638        now happen in IcsExamining mode, but that's just too bad.  The user
6639        will get a somewhat confusing message in that case.)
6640        */
6641
6642     switch (gameMode) {
6643       case AnalyzeFile:
6644       case TwoMachinesPlay:
6645       case EndOfGame:
6646       case IcsObserving:
6647       case IcsIdle:
6648         /* We switched into a game mode where moves are not accepted,
6649            perhaps while the mouse button was down. */
6650         return;
6651
6652       case MachinePlaysWhite:
6653         /* User is moving for Black */
6654         if (WhiteOnMove(currentMove)) {
6655             DisplayMoveError(_("It is White's turn"));
6656             return;
6657         }
6658         break;
6659
6660       case MachinePlaysBlack:
6661         /* User is moving for White */
6662         if (!WhiteOnMove(currentMove)) {
6663             DisplayMoveError(_("It is Black's turn"));
6664             return;
6665         }
6666         break;
6667
6668       case PlayFromGameFile:
6669             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6670       case EditGame:
6671       case IcsExamining:
6672       case BeginningOfGame:
6673       case AnalyzeMode:
6674       case Training:
6675         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6676         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6677             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6678             /* User is moving for Black */
6679             if (WhiteOnMove(currentMove)) {
6680                 DisplayMoveError(_("It is White's turn"));
6681                 return;
6682             }
6683         } else {
6684             /* User is moving for White */
6685             if (!WhiteOnMove(currentMove)) {
6686                 DisplayMoveError(_("It is Black's turn"));
6687                 return;
6688             }
6689         }
6690         break;
6691
6692       case IcsPlayingBlack:
6693         /* User is moving for Black */
6694         if (WhiteOnMove(currentMove)) {
6695             if (!appData.premove) {
6696                 DisplayMoveError(_("It is White's turn"));
6697             } else if (toX >= 0 && toY >= 0) {
6698                 premoveToX = toX;
6699                 premoveToY = toY;
6700                 premoveFromX = fromX;
6701                 premoveFromY = fromY;
6702                 premovePromoChar = promoChar;
6703                 gotPremove = 1;
6704                 if (appData.debugMode)
6705                     fprintf(debugFP, "Got premove: fromX %d,"
6706                             "fromY %d, toX %d, toY %d\n",
6707                             fromX, fromY, toX, toY);
6708             }
6709             return;
6710         }
6711         break;
6712
6713       case IcsPlayingWhite:
6714         /* User is moving for White */
6715         if (!WhiteOnMove(currentMove)) {
6716             if (!appData.premove) {
6717                 DisplayMoveError(_("It is Black's turn"));
6718             } else if (toX >= 0 && toY >= 0) {
6719                 premoveToX = toX;
6720                 premoveToY = toY;
6721                 premoveFromX = fromX;
6722                 premoveFromY = fromY;
6723                 premovePromoChar = promoChar;
6724                 gotPremove = 1;
6725                 if (appData.debugMode)
6726                     fprintf(debugFP, "Got premove: fromX %d,"
6727                             "fromY %d, toX %d, toY %d\n",
6728                             fromX, fromY, toX, toY);
6729             }
6730             return;
6731         }
6732         break;
6733
6734       default:
6735         break;
6736
6737       case EditPosition:
6738         /* EditPosition, empty square, or different color piece;
6739            click-click move is possible */
6740         if (toX == -2 || toY == -2) {
6741             boards[0][fromY][fromX] = EmptySquare;
6742             DrawPosition(FALSE, boards[currentMove]);
6743             return;
6744         } else if (toX >= 0 && toY >= 0) {
6745             boards[0][toY][toX] = boards[0][fromY][fromX];
6746             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6747                 if(boards[0][fromY][0] != EmptySquare) {
6748                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6749                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6750                 }
6751             } else
6752             if(fromX == BOARD_RGHT+1) {
6753                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6754                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6755                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6756                 }
6757             } else
6758             boards[0][fromY][fromX] = gatingPiece;
6759             DrawPosition(FALSE, boards[currentMove]);
6760             return;
6761         }
6762         return;
6763     }
6764
6765     if(toX < 0 || toY < 0) return;
6766     pup = boards[currentMove][toY][toX];
6767
6768     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6769     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6770          if( pup != EmptySquare ) return;
6771          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6772            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6773                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6774            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6775            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6776            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6777            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6778          fromY = DROP_RANK;
6779     }
6780
6781     /* [HGM] always test for legality, to get promotion info */
6782     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6783                                          fromY, fromX, toY, toX, promoChar);
6784
6785     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6786
6787     /* [HGM] but possibly ignore an IllegalMove result */
6788     if (appData.testLegality) {
6789         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6790             DisplayMoveError(_("Illegal move"));
6791             return;
6792         }
6793     }
6794
6795     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6796         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6797              ClearPremoveHighlights(); // was included
6798         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6799         return;
6800     }
6801
6802     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6803 }
6804
6805 /* Common tail of UserMoveEvent and DropMenuEvent */
6806 int
6807 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6808 {
6809     char *bookHit = 0;
6810
6811     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6812         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6813         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6814         if(WhiteOnMove(currentMove)) {
6815             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6816         } else {
6817             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6818         }
6819     }
6820
6821     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6822        move type in caller when we know the move is a legal promotion */
6823     if(moveType == NormalMove && promoChar)
6824         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6825
6826     /* [HGM] <popupFix> The following if has been moved here from
6827        UserMoveEvent(). Because it seemed to belong here (why not allow
6828        piece drops in training games?), and because it can only be
6829        performed after it is known to what we promote. */
6830     if (gameMode == Training) {
6831       /* compare the move played on the board to the next move in the
6832        * game. If they match, display the move and the opponent's response.
6833        * If they don't match, display an error message.
6834        */
6835       int saveAnimate;
6836       Board testBoard;
6837       CopyBoard(testBoard, boards[currentMove]);
6838       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6839
6840       if (CompareBoards(testBoard, boards[currentMove+1])) {
6841         ForwardInner(currentMove+1);
6842
6843         /* Autoplay the opponent's response.
6844          * if appData.animate was TRUE when Training mode was entered,
6845          * the response will be animated.
6846          */
6847         saveAnimate = appData.animate;
6848         appData.animate = animateTraining;
6849         ForwardInner(currentMove+1);
6850         appData.animate = saveAnimate;
6851
6852         /* check for the end of the game */
6853         if (currentMove >= forwardMostMove) {
6854           gameMode = PlayFromGameFile;
6855           ModeHighlight();
6856           SetTrainingModeOff();
6857           DisplayInformation(_("End of game"));
6858         }
6859       } else {
6860         DisplayError(_("Incorrect move"), 0);
6861       }
6862       return 1;
6863     }
6864
6865   /* Ok, now we know that the move is good, so we can kill
6866      the previous line in Analysis Mode */
6867   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6868                                 && currentMove < forwardMostMove) {
6869     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6870     else forwardMostMove = currentMove;
6871   }
6872
6873   ClearMap();
6874
6875   /* If we need the chess program but it's dead, restart it */
6876   ResurrectChessProgram();
6877
6878   /* A user move restarts a paused game*/
6879   if (pausing)
6880     PauseEvent();
6881
6882   thinkOutput[0] = NULLCHAR;
6883
6884   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6885
6886   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6887     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6888     return 1;
6889   }
6890
6891   if (gameMode == BeginningOfGame) {
6892     if (appData.noChessProgram) {
6893       gameMode = EditGame;
6894       SetGameInfo();
6895     } else {
6896       char buf[MSG_SIZ];
6897       gameMode = MachinePlaysBlack;
6898       StartClocks();
6899       SetGameInfo();
6900       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6901       DisplayTitle(buf);
6902       if (first.sendName) {
6903         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6904         SendToProgram(buf, &first);
6905       }
6906       StartClocks();
6907     }
6908     ModeHighlight();
6909   }
6910
6911   /* Relay move to ICS or chess engine */
6912   if (appData.icsActive) {
6913     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6914         gameMode == IcsExamining) {
6915       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6916         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6917         SendToICS("draw ");
6918         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6919       }
6920       // also send plain move, in case ICS does not understand atomic claims
6921       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6922       ics_user_moved = 1;
6923     }
6924   } else {
6925     if (first.sendTime && (gameMode == BeginningOfGame ||
6926                            gameMode == MachinePlaysWhite ||
6927                            gameMode == MachinePlaysBlack)) {
6928       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6929     }
6930     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6931          // [HGM] book: if program might be playing, let it use book
6932         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6933         first.maybeThinking = TRUE;
6934     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6935         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6936         SendBoard(&first, currentMove+1);
6937         if(second.analyzing) {
6938             if(!second.useSetboard) SendToProgram("undo\n", &second);
6939             SendBoard(&second, currentMove+1);
6940         }
6941     } else {
6942         SendMoveToProgram(forwardMostMove-1, &first);
6943         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6944     }
6945     if (currentMove == cmailOldMove + 1) {
6946       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6947     }
6948   }
6949
6950   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6951
6952   switch (gameMode) {
6953   case EditGame:
6954     if(appData.testLegality)
6955     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6956     case MT_NONE:
6957     case MT_CHECK:
6958       break;
6959     case MT_CHECKMATE:
6960     case MT_STAINMATE:
6961       if (WhiteOnMove(currentMove)) {
6962         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6963       } else {
6964         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6965       }
6966       break;
6967     case MT_STALEMATE:
6968       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6969       break;
6970     }
6971     break;
6972
6973   case MachinePlaysBlack:
6974   case MachinePlaysWhite:
6975     /* disable certain menu options while machine is thinking */
6976     SetMachineThinkingEnables();
6977     break;
6978
6979   default:
6980     break;
6981   }
6982
6983   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6984   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6985
6986   if(bookHit) { // [HGM] book: simulate book reply
6987         static char bookMove[MSG_SIZ]; // a bit generous?
6988
6989         programStats.nodes = programStats.depth = programStats.time =
6990         programStats.score = programStats.got_only_move = 0;
6991         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6992
6993         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6994         strcat(bookMove, bookHit);
6995         HandleMachineMove(bookMove, &first);
6996   }
6997   return 1;
6998 }
6999
7000 void
7001 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7002 {
7003     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7004     Markers *m = (Markers *) closure;
7005     if(rf == fromY && ff == fromX)
7006         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7007                          || kind == WhiteCapturesEnPassant
7008                          || kind == BlackCapturesEnPassant);
7009     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7010 }
7011
7012 void
7013 MarkTargetSquares (int clear)
7014 {
7015   int x, y;
7016   if(clear) // no reason to ever suppress clearing
7017     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
7018   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7019      !appData.testLegality || gameMode == EditPosition) return;
7020   if(!clear) {
7021     int capt = 0;
7022     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7023     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7024       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7025       if(capt)
7026       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7027     }
7028   }
7029   DrawPosition(FALSE, NULL);
7030 }
7031
7032 int
7033 Explode (Board board, int fromX, int fromY, int toX, int toY)
7034 {
7035     if(gameInfo.variant == VariantAtomic &&
7036        (board[toY][toX] != EmptySquare ||                     // capture?
7037         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7038                          board[fromY][fromX] == BlackPawn   )
7039       )) {
7040         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7041         return TRUE;
7042     }
7043     return FALSE;
7044 }
7045
7046 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7047
7048 int
7049 CanPromote (ChessSquare piece, int y)
7050 {
7051         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7052         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7053         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7054            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7055            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7056                                                   gameInfo.variant == VariantMakruk) return FALSE;
7057         return (piece == BlackPawn && y == 1 ||
7058                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7059                 piece == BlackLance && y == 1 ||
7060                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7061 }
7062
7063 void
7064 LeftClick (ClickType clickType, int xPix, int yPix)
7065 {
7066     int x, y;
7067     Boolean saveAnimate;
7068     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7069     char promoChoice = NULLCHAR;
7070     ChessSquare piece;
7071     static TimeMark lastClickTime, prevClickTime;
7072
7073     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7074
7075     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7076
7077     if (clickType == Press) ErrorPopDown();
7078
7079     x = EventToSquare(xPix, BOARD_WIDTH);
7080     y = EventToSquare(yPix, BOARD_HEIGHT);
7081     if (!flipView && y >= 0) {
7082         y = BOARD_HEIGHT - 1 - y;
7083     }
7084     if (flipView && x >= 0) {
7085         x = BOARD_WIDTH - 1 - x;
7086     }
7087
7088     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7089         defaultPromoChoice = promoSweep;
7090         promoSweep = EmptySquare;   // terminate sweep
7091         promoDefaultAltered = TRUE;
7092         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7093     }
7094
7095     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7096         if(clickType == Release) return; // ignore upclick of click-click destination
7097         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7098         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7099         if(gameInfo.holdingsWidth &&
7100                 (WhiteOnMove(currentMove)
7101                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7102                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7103             // click in right holdings, for determining promotion piece
7104             ChessSquare p = boards[currentMove][y][x];
7105             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7106             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7107             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7108                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7109                 fromX = fromY = -1;
7110                 return;
7111             }
7112         }
7113         DrawPosition(FALSE, boards[currentMove]);
7114         return;
7115     }
7116
7117     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7118     if(clickType == Press
7119             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7120               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7121               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7122         return;
7123
7124     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7125         // could be static click on premove from-square: abort premove
7126         gotPremove = 0;
7127         ClearPremoveHighlights();
7128     }
7129
7130     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7131         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7132
7133     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7134         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7135                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7136         defaultPromoChoice = DefaultPromoChoice(side);
7137     }
7138
7139     autoQueen = appData.alwaysPromoteToQueen;
7140
7141     if (fromX == -1) {
7142       int originalY = y;
7143       gatingPiece = EmptySquare;
7144       if (clickType != Press) {
7145         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7146             DragPieceEnd(xPix, yPix); dragging = 0;
7147             DrawPosition(FALSE, NULL);
7148         }
7149         return;
7150       }
7151       doubleClick = FALSE;
7152       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7153         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7154       }
7155       fromX = x; fromY = y; toX = toY = -1;
7156       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7157          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7158          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7159             /* First square */
7160             if (OKToStartUserMove(fromX, fromY)) {
7161                 second = 0;
7162                 MarkTargetSquares(0);
7163                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7164                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7165                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7166                     promoSweep = defaultPromoChoice;
7167                     selectFlag = 0; lastX = xPix; lastY = yPix;
7168                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7169                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7170                 }
7171                 if (appData.highlightDragging) {
7172                     SetHighlights(fromX, fromY, -1, -1);
7173                 } else {
7174                     ClearHighlights();
7175                 }
7176             } else fromX = fromY = -1;
7177             return;
7178         }
7179     }
7180
7181     /* fromX != -1 */
7182     if (clickType == Press && gameMode != EditPosition) {
7183         ChessSquare fromP;
7184         ChessSquare toP;
7185         int frc;
7186
7187         // ignore off-board to clicks
7188         if(y < 0 || x < 0) return;
7189
7190         /* Check if clicking again on the same color piece */
7191         fromP = boards[currentMove][fromY][fromX];
7192         toP = boards[currentMove][y][x];
7193         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7194         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7195              WhitePawn <= toP && toP <= WhiteKing &&
7196              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7197              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7198             (BlackPawn <= fromP && fromP <= BlackKing &&
7199              BlackPawn <= toP && toP <= BlackKing &&
7200              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7201              !(fromP == BlackKing && toP == BlackRook && frc))) {
7202             /* Clicked again on same color piece -- changed his mind */
7203             second = (x == fromX && y == fromY);
7204             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7205                 second = FALSE; // first double-click rather than scond click
7206                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7207             }
7208             promoDefaultAltered = FALSE;
7209             MarkTargetSquares(1);
7210            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7211             if (appData.highlightDragging) {
7212                 SetHighlights(x, y, -1, -1);
7213             } else {
7214                 ClearHighlights();
7215             }
7216             if (OKToStartUserMove(x, y)) {
7217                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7218                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7219                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7220                  gatingPiece = boards[currentMove][fromY][fromX];
7221                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7222                 fromX = x;
7223                 fromY = y; dragging = 1;
7224                 MarkTargetSquares(0);
7225                 DragPieceBegin(xPix, yPix, FALSE);
7226                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7227                     promoSweep = defaultPromoChoice;
7228                     selectFlag = 0; lastX = xPix; lastY = yPix;
7229                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7230                 }
7231             }
7232            }
7233            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7234            second = FALSE;
7235         }
7236         // ignore clicks on holdings
7237         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7238     }
7239
7240     if (clickType == Release && x == fromX && y == fromY) {
7241         DragPieceEnd(xPix, yPix); dragging = 0;
7242         if(clearFlag) {
7243             // a deferred attempt to click-click move an empty square on top of a piece
7244             boards[currentMove][y][x] = EmptySquare;
7245             ClearHighlights();
7246             DrawPosition(FALSE, boards[currentMove]);
7247             fromX = fromY = -1; clearFlag = 0;
7248             return;
7249         }
7250         if (appData.animateDragging) {
7251             /* Undo animation damage if any */
7252             DrawPosition(FALSE, NULL);
7253         }
7254         if (second || sweepSelecting) {
7255             /* Second up/down in same square; just abort move */
7256             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7257             second = sweepSelecting = 0;
7258             fromX = fromY = -1;
7259             gatingPiece = EmptySquare;
7260             ClearHighlights();
7261             gotPremove = 0;
7262             ClearPremoveHighlights();
7263         } else {
7264             /* First upclick in same square; start click-click mode */
7265             SetHighlights(x, y, -1, -1);
7266         }
7267         return;
7268     }
7269
7270     clearFlag = 0;
7271
7272     /* we now have a different from- and (possibly off-board) to-square */
7273     /* Completed move */
7274     if(!sweepSelecting) {
7275         toX = x;
7276         toY = y;
7277     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7278
7279     saveAnimate = appData.animate;
7280     if (clickType == Press) {
7281         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7282             // must be Edit Position mode with empty-square selected
7283             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7284             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7285             return;
7286         }
7287         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7288           if(appData.sweepSelect) {
7289             ChessSquare piece = boards[currentMove][fromY][fromX];
7290             promoSweep = defaultPromoChoice;
7291             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7292             selectFlag = 0; lastX = xPix; lastY = yPix;
7293             Sweep(0); // Pawn that is going to promote: preview promotion piece
7294             sweepSelecting = 1;
7295             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7296             MarkTargetSquares(1);
7297           }
7298           return; // promo popup appears on up-click
7299         }
7300         /* Finish clickclick move */
7301         if (appData.animate || appData.highlightLastMove) {
7302             SetHighlights(fromX, fromY, toX, toY);
7303         } else {
7304             ClearHighlights();
7305         }
7306     } else {
7307 #if 0
7308 // [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
7309         /* Finish drag move */
7310         if (appData.highlightLastMove) {
7311             SetHighlights(fromX, fromY, toX, toY);
7312         } else {
7313             ClearHighlights();
7314         }
7315 #endif
7316         DragPieceEnd(xPix, yPix); dragging = 0;
7317         /* Don't animate move and drag both */
7318         appData.animate = FALSE;
7319     }
7320
7321     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7322     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7323         ChessSquare piece = boards[currentMove][fromY][fromX];
7324         if(gameMode == EditPosition && piece != EmptySquare &&
7325            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7326             int n;
7327
7328             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7329                 n = PieceToNumber(piece - (int)BlackPawn);
7330                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7331                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7332                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7333             } else
7334             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7335                 n = PieceToNumber(piece);
7336                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7337                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7338                 boards[currentMove][n][BOARD_WIDTH-2]++;
7339             }
7340             boards[currentMove][fromY][fromX] = EmptySquare;
7341         }
7342         ClearHighlights();
7343         fromX = fromY = -1;
7344         MarkTargetSquares(1);
7345         DrawPosition(TRUE, boards[currentMove]);
7346         return;
7347     }
7348
7349     // off-board moves should not be highlighted
7350     if(x < 0 || y < 0) ClearHighlights();
7351
7352     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7353
7354     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7355         SetHighlights(fromX, fromY, toX, toY);
7356         MarkTargetSquares(1);
7357         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7358             // [HGM] super: promotion to captured piece selected from holdings
7359             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7360             promotionChoice = TRUE;
7361             // kludge follows to temporarily execute move on display, without promoting yet
7362             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7363             boards[currentMove][toY][toX] = p;
7364             DrawPosition(FALSE, boards[currentMove]);
7365             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7366             boards[currentMove][toY][toX] = q;
7367             DisplayMessage("Click in holdings to choose piece", "");
7368             return;
7369         }
7370         PromotionPopUp();
7371     } else {
7372         int oldMove = currentMove;
7373         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7374         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7375         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7376         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7377            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7378             DrawPosition(TRUE, boards[currentMove]);
7379         MarkTargetSquares(1);
7380         fromX = fromY = -1;
7381     }
7382     appData.animate = saveAnimate;
7383     if (appData.animate || appData.animateDragging) {
7384         /* Undo animation damage if needed */
7385         DrawPosition(FALSE, NULL);
7386     }
7387 }
7388
7389 int
7390 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7391 {   // front-end-free part taken out of PieceMenuPopup
7392     int whichMenu; int xSqr, ySqr;
7393
7394     if(seekGraphUp) { // [HGM] seekgraph
7395         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7396         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7397         return -2;
7398     }
7399
7400     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7401          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7402         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7403         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7404         if(action == Press)   {
7405             originalFlip = flipView;
7406             flipView = !flipView; // temporarily flip board to see game from partners perspective
7407             DrawPosition(TRUE, partnerBoard);
7408             DisplayMessage(partnerStatus, "");
7409             partnerUp = TRUE;
7410         } else if(action == Release) {
7411             flipView = originalFlip;
7412             DrawPosition(TRUE, boards[currentMove]);
7413             partnerUp = FALSE;
7414         }
7415         return -2;
7416     }
7417
7418     xSqr = EventToSquare(x, BOARD_WIDTH);
7419     ySqr = EventToSquare(y, BOARD_HEIGHT);
7420     if (action == Release) {
7421         if(pieceSweep != EmptySquare) {
7422             EditPositionMenuEvent(pieceSweep, toX, toY);
7423             pieceSweep = EmptySquare;
7424         } else UnLoadPV(); // [HGM] pv
7425     }
7426     if (action != Press) return -2; // return code to be ignored
7427     switch (gameMode) {
7428       case IcsExamining:
7429         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7430       case EditPosition:
7431         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7432         if (xSqr < 0 || ySqr < 0) return -1;
7433         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7434         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7435         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7436         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7437         NextPiece(0);
7438         return 2; // grab
7439       case IcsObserving:
7440         if(!appData.icsEngineAnalyze) return -1;
7441       case IcsPlayingWhite:
7442       case IcsPlayingBlack:
7443         if(!appData.zippyPlay) goto noZip;
7444       case AnalyzeMode:
7445       case AnalyzeFile:
7446       case MachinePlaysWhite:
7447       case MachinePlaysBlack:
7448       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7449         if (!appData.dropMenu) {
7450           LoadPV(x, y);
7451           return 2; // flag front-end to grab mouse events
7452         }
7453         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7454            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7455       case EditGame:
7456       noZip:
7457         if (xSqr < 0 || ySqr < 0) return -1;
7458         if (!appData.dropMenu || appData.testLegality &&
7459             gameInfo.variant != VariantBughouse &&
7460             gameInfo.variant != VariantCrazyhouse) return -1;
7461         whichMenu = 1; // drop menu
7462         break;
7463       default:
7464         return -1;
7465     }
7466
7467     if (((*fromX = xSqr) < 0) ||
7468         ((*fromY = ySqr) < 0)) {
7469         *fromX = *fromY = -1;
7470         return -1;
7471     }
7472     if (flipView)
7473       *fromX = BOARD_WIDTH - 1 - *fromX;
7474     else
7475       *fromY = BOARD_HEIGHT - 1 - *fromY;
7476
7477     return whichMenu;
7478 }
7479
7480 void
7481 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7482 {
7483 //    char * hint = lastHint;
7484     FrontEndProgramStats stats;
7485
7486     stats.which = cps == &first ? 0 : 1;
7487     stats.depth = cpstats->depth;
7488     stats.nodes = cpstats->nodes;
7489     stats.score = cpstats->score;
7490     stats.time = cpstats->time;
7491     stats.pv = cpstats->movelist;
7492     stats.hint = lastHint;
7493     stats.an_move_index = 0;
7494     stats.an_move_count = 0;
7495
7496     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7497         stats.hint = cpstats->move_name;
7498         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7499         stats.an_move_count = cpstats->nr_moves;
7500     }
7501
7502     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
7503
7504     SetProgramStats( &stats );
7505 }
7506
7507 void
7508 ClearEngineOutputPane (int which)
7509 {
7510     static FrontEndProgramStats dummyStats;
7511     dummyStats.which = which;
7512     dummyStats.pv = "#";
7513     SetProgramStats( &dummyStats );
7514 }
7515
7516 #define MAXPLAYERS 500
7517
7518 char *
7519 TourneyStandings (int display)
7520 {
7521     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7522     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7523     char result, *p, *names[MAXPLAYERS];
7524
7525     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7526         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7527     names[0] = p = strdup(appData.participants);
7528     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7529
7530     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7531
7532     while(result = appData.results[nr]) {
7533         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7534         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7535         wScore = bScore = 0;
7536         switch(result) {
7537           case '+': wScore = 2; break;
7538           case '-': bScore = 2; break;
7539           case '=': wScore = bScore = 1; break;
7540           case ' ':
7541           case '*': return strdup("busy"); // tourney not finished
7542         }
7543         score[w] += wScore;
7544         score[b] += bScore;
7545         games[w]++;
7546         games[b]++;
7547         nr++;
7548     }
7549     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7550     for(w=0; w<nPlayers; w++) {
7551         bScore = -1;
7552         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7553         ranking[w] = b; points[w] = bScore; score[b] = -2;
7554     }
7555     p = malloc(nPlayers*34+1);
7556     for(w=0; w<nPlayers && w<display; w++)
7557         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7558     free(names[0]);
7559     return p;
7560 }
7561
7562 void
7563 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7564 {       // count all piece types
7565         int p, f, r;
7566         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7567         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7568         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7569                 p = board[r][f];
7570                 pCnt[p]++;
7571                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7572                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7573                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7574                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7575                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7576                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7577         }
7578 }
7579
7580 int
7581 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7582 {
7583         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7584         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7585
7586         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7587         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7588         if(myPawns == 2 && nMine == 3) // KPP
7589             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7590         if(myPawns == 1 && nMine == 2) // KP
7591             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7592         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7593             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7594         if(myPawns) return FALSE;
7595         if(pCnt[WhiteRook+side])
7596             return pCnt[BlackRook-side] ||
7597                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7598                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7599                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7600         if(pCnt[WhiteCannon+side]) {
7601             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7602             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7603         }
7604         if(pCnt[WhiteKnight+side])
7605             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7606         return FALSE;
7607 }
7608
7609 int
7610 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7611 {
7612         VariantClass v = gameInfo.variant;
7613
7614         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7615         if(v == VariantShatranj) return TRUE; // always winnable through baring
7616         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7617         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7618
7619         if(v == VariantXiangqi) {
7620                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7621
7622                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7623                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7624                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7625                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7626                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7627                 if(stale) // we have at least one last-rank P plus perhaps C
7628                     return majors // KPKX
7629                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7630                 else // KCA*E*
7631                     return pCnt[WhiteFerz+side] // KCAK
7632                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7633                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7634                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7635
7636         } else if(v == VariantKnightmate) {
7637                 if(nMine == 1) return FALSE;
7638                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7639         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7640                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7641
7642                 if(nMine == 1) return FALSE; // bare King
7643                 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
7644                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7645                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7646                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7647                 if(pCnt[WhiteKnight+side])
7648                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7649                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7650                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7651                 if(nBishops)
7652                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7653                 if(pCnt[WhiteAlfil+side])
7654                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7655                 if(pCnt[WhiteWazir+side])
7656                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7657         }
7658
7659         return TRUE;
7660 }
7661
7662 int
7663 CompareWithRights (Board b1, Board b2)
7664 {
7665     int rights = 0;
7666     if(!CompareBoards(b1, b2)) return FALSE;
7667     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7668     /* compare castling rights */
7669     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7670            rights++; /* King lost rights, while rook still had them */
7671     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7672         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7673            rights++; /* but at least one rook lost them */
7674     }
7675     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7676            rights++;
7677     if( b1[CASTLING][5] != NoRights ) {
7678         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7679            rights++;
7680     }
7681     return rights == 0;
7682 }
7683
7684 int
7685 Adjudicate (ChessProgramState *cps)
7686 {       // [HGM] some adjudications useful with buggy engines
7687         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7688         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7689         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7690         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7691         int k, drop, count = 0; static int bare = 1;
7692         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7693         Boolean canAdjudicate = !appData.icsActive;
7694
7695         // most tests only when we understand the game, i.e. legality-checking on
7696             if( appData.testLegality )
7697             {   /* [HGM] Some more adjudications for obstinate engines */
7698                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7699                 static int moveCount = 6;
7700                 ChessMove result;
7701                 char *reason = NULL;
7702
7703                 /* Count what is on board. */
7704                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7705
7706                 /* Some material-based adjudications that have to be made before stalemate test */
7707                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7708                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7709                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7710                      if(canAdjudicate && appData.checkMates) {
7711                          if(engineOpponent)
7712                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7713                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7714                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7715                          return 1;
7716                      }
7717                 }
7718
7719                 /* Bare King in Shatranj (loses) or Losers (wins) */
7720                 if( nrW == 1 || nrB == 1) {
7721                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7722                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7723                      if(canAdjudicate && appData.checkMates) {
7724                          if(engineOpponent)
7725                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7726                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7727                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7728                          return 1;
7729                      }
7730                   } else
7731                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7732                   {    /* bare King */
7733                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7734                         if(canAdjudicate && appData.checkMates) {
7735                             /* but only adjudicate if adjudication enabled */
7736                             if(engineOpponent)
7737                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7738                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7739                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7740                             return 1;
7741                         }
7742                   }
7743                 } else bare = 1;
7744
7745
7746             // don't wait for engine to announce game end if we can judge ourselves
7747             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7748               case MT_CHECK:
7749                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7750                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7751                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7752                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7753                             checkCnt++;
7754                         if(checkCnt >= 2) {
7755                             reason = "Xboard adjudication: 3rd check";
7756                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7757                             break;
7758                         }
7759                     }
7760                 }
7761               case MT_NONE:
7762               default:
7763                 break;
7764               case MT_STALEMATE:
7765               case MT_STAINMATE:
7766                 reason = "Xboard adjudication: Stalemate";
7767                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7768                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7769                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7770                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7771                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7772                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7773                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7774                                                                         EP_CHECKMATE : EP_WINS);
7775                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7776                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7777                 }
7778                 break;
7779               case MT_CHECKMATE:
7780                 reason = "Xboard adjudication: Checkmate";
7781                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7782                 if(gameInfo.variant == VariantShogi) {
7783                     if(forwardMostMove > backwardMostMove
7784                        && moveList[forwardMostMove-1][1] == '@'
7785                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7786                         reason = "XBoard adjudication: pawn-drop mate";
7787                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7788                     }
7789                 }
7790                 break;
7791             }
7792
7793                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7794                     case EP_STALEMATE:
7795                         result = GameIsDrawn; break;
7796                     case EP_CHECKMATE:
7797                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7798                     case EP_WINS:
7799                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7800                     default:
7801                         result = EndOfFile;
7802                 }
7803                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7804                     if(engineOpponent)
7805                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7806                     GameEnds( result, reason, GE_XBOARD );
7807                     return 1;
7808                 }
7809
7810                 /* Next absolutely insufficient mating material. */
7811                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7812                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7813                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7814
7815                      /* always flag draws, for judging claims */
7816                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7817
7818                      if(canAdjudicate && appData.materialDraws) {
7819                          /* but only adjudicate them if adjudication enabled */
7820                          if(engineOpponent) {
7821                            SendToProgram("force\n", engineOpponent); // suppress reply
7822                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7823                          }
7824                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7825                          return 1;
7826                      }
7827                 }
7828
7829                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7830                 if(gameInfo.variant == VariantXiangqi ?
7831                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7832                  : nrW + nrB == 4 &&
7833                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7834                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7835                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7836                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7837                    ) ) {
7838                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7839                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7840                           if(engineOpponent) {
7841                             SendToProgram("force\n", engineOpponent); // suppress reply
7842                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7843                           }
7844                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7845                           return 1;
7846                      }
7847                 } else moveCount = 6;
7848             }
7849
7850         // Repetition draws and 50-move rule can be applied independently of legality testing
7851
7852                 /* Check for rep-draws */
7853                 count = 0;
7854                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
7855                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
7856                 for(k = forwardMostMove-2;
7857                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
7858                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7859                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
7860                     k-=2)
7861                 {   int rights=0;
7862                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7863                         /* compare castling rights */
7864                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7865                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7866                                 rights++; /* King lost rights, while rook still had them */
7867                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7868                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7869                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7870                                    rights++; /* but at least one rook lost them */
7871                         }
7872                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7873                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7874                                 rights++;
7875                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7876                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7877                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7878                                    rights++;
7879                         }
7880                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7881                             && appData.drawRepeats > 1) {
7882                              /* adjudicate after user-specified nr of repeats */
7883                              int result = GameIsDrawn;
7884                              char *details = "XBoard adjudication: repetition draw";
7885                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
7886                                 // [HGM] xiangqi: check for forbidden perpetuals
7887                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7888                                 for(m=forwardMostMove; m>k; m-=2) {
7889                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7890                                         ourPerpetual = 0; // the current mover did not always check
7891                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7892                                         hisPerpetual = 0; // the opponent did not always check
7893                                 }
7894                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7895                                                                         ourPerpetual, hisPerpetual);
7896                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7897                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7898                                     details = "Xboard adjudication: perpetual checking";
7899                                 } else
7900                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7901                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7902                                 } else
7903                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
7904                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
7905                                         result = BlackWins;
7906                                         details = "Xboard adjudication: repetition";
7907                                     }
7908                                 } else // it must be XQ
7909                                 // Now check for perpetual chases
7910                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7911                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7912                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7913                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7914                                         static char resdet[MSG_SIZ];
7915                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7916                                         details = resdet;
7917                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7918                                     } else
7919                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7920                                         break; // Abort repetition-checking loop.
7921                                 }
7922                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7923                              }
7924                              if(engineOpponent) {
7925                                SendToProgram("force\n", engineOpponent); // suppress reply
7926                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7927                              }
7928                              GameEnds( result, details, GE_XBOARD );
7929                              return 1;
7930                         }
7931                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7932                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7933                     }
7934                 }
7935
7936                 /* Now we test for 50-move draws. Determine ply count */
7937                 count = forwardMostMove;
7938                 /* look for last irreversble move */
7939                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7940                     count--;
7941                 /* if we hit starting position, add initial plies */
7942                 if( count == backwardMostMove )
7943                     count -= initialRulePlies;
7944                 count = forwardMostMove - count;
7945                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7946                         // adjust reversible move counter for checks in Xiangqi
7947                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7948                         if(i < backwardMostMove) i = backwardMostMove;
7949                         while(i <= forwardMostMove) {
7950                                 lastCheck = inCheck; // check evasion does not count
7951                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7952                                 if(inCheck || lastCheck) count--; // check does not count
7953                                 i++;
7954                         }
7955                 }
7956                 if( count >= 100)
7957                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7958                          /* this is used to judge if draw claims are legal */
7959                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7960                          if(engineOpponent) {
7961                            SendToProgram("force\n", engineOpponent); // suppress reply
7962                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7963                          }
7964                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7965                          return 1;
7966                 }
7967
7968                 /* if draw offer is pending, treat it as a draw claim
7969                  * when draw condition present, to allow engines a way to
7970                  * claim draws before making their move to avoid a race
7971                  * condition occurring after their move
7972                  */
7973                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7974                          char *p = NULL;
7975                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7976                              p = "Draw claim: 50-move rule";
7977                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7978                              p = "Draw claim: 3-fold repetition";
7979                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7980                              p = "Draw claim: insufficient mating material";
7981                          if( p != NULL && canAdjudicate) {
7982                              if(engineOpponent) {
7983                                SendToProgram("force\n", engineOpponent); // suppress reply
7984                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7985                              }
7986                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7987                              return 1;
7988                          }
7989                 }
7990
7991                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7992                     if(engineOpponent) {
7993                       SendToProgram("force\n", engineOpponent); // suppress reply
7994                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7995                     }
7996                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7997                     return 1;
7998                 }
7999         return 0;
8000 }
8001
8002 char *
8003 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8004 {   // [HGM] book: this routine intercepts moves to simulate book replies
8005     char *bookHit = NULL;
8006
8007     //first determine if the incoming move brings opponent into his book
8008     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8009         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8010     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8011     if(bookHit != NULL && !cps->bookSuspend) {
8012         // make sure opponent is not going to reply after receiving move to book position
8013         SendToProgram("force\n", cps);
8014         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8015     }
8016     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8017     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8018     // now arrange restart after book miss
8019     if(bookHit) {
8020         // after a book hit we never send 'go', and the code after the call to this routine
8021         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8022         char buf[MSG_SIZ], *move = bookHit;
8023         if(cps->useSAN) {
8024             int fromX, fromY, toX, toY;
8025             char promoChar;
8026             ChessMove moveType;
8027             move = buf + 30;
8028             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8029                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8030                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8031                                     PosFlags(forwardMostMove),
8032                                     fromY, fromX, toY, toX, promoChar, move);
8033             } else {
8034                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8035                 bookHit = NULL;
8036             }
8037         }
8038         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8039         SendToProgram(buf, cps);
8040         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8041     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8042         SendToProgram("go\n", cps);
8043         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8044     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8045         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8046             SendToProgram("go\n", cps);
8047         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8048     }
8049     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8050 }
8051
8052 int
8053 LoadError (char *errmess, ChessProgramState *cps)
8054 {   // unloads engine and switches back to -ncp mode if it was first
8055     if(cps->initDone) return FALSE;
8056     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8057     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8058     cps->pr = NoProc;
8059     if(cps == &first) {
8060         appData.noChessProgram = TRUE;
8061         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8062         gameMode = BeginningOfGame; ModeHighlight();
8063         SetNCPMode();
8064     }
8065     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8066     DisplayMessage("", ""); // erase waiting message
8067     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8068     return TRUE;
8069 }
8070
8071 char *savedMessage;
8072 ChessProgramState *savedState;
8073 void
8074 DeferredBookMove (void)
8075 {
8076         if(savedState->lastPing != savedState->lastPong)
8077                     ScheduleDelayedEvent(DeferredBookMove, 10);
8078         else
8079         HandleMachineMove(savedMessage, savedState);
8080 }
8081
8082 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8083 static ChessProgramState *stalledEngine;
8084 static char stashedInputMove[MSG_SIZ];
8085
8086 void
8087 HandleMachineMove (char *message, ChessProgramState *cps)
8088 {
8089     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8090     char realname[MSG_SIZ];
8091     int fromX, fromY, toX, toY;
8092     ChessMove moveType;
8093     char promoChar;
8094     char *p, *pv=buf1;
8095     int machineWhite, oldError;
8096     char *bookHit;
8097
8098     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8099         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8100         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8101             DisplayError(_("Invalid pairing from pairing engine"), 0);
8102             return;
8103         }
8104         pairingReceived = 1;
8105         NextMatchGame();
8106         return; // Skim the pairing messages here.
8107     }
8108
8109     oldError = cps->userError; cps->userError = 0;
8110
8111 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8112     /*
8113      * Kludge to ignore BEL characters
8114      */
8115     while (*message == '\007') message++;
8116
8117     /*
8118      * [HGM] engine debug message: ignore lines starting with '#' character
8119      */
8120     if(cps->debug && *message == '#') return;
8121
8122     /*
8123      * Look for book output
8124      */
8125     if (cps == &first && bookRequested) {
8126         if (message[0] == '\t' || message[0] == ' ') {
8127             /* Part of the book output is here; append it */
8128             strcat(bookOutput, message);
8129             strcat(bookOutput, "  \n");
8130             return;
8131         } else if (bookOutput[0] != NULLCHAR) {
8132             /* All of book output has arrived; display it */
8133             char *p = bookOutput;
8134             while (*p != NULLCHAR) {
8135                 if (*p == '\t') *p = ' ';
8136                 p++;
8137             }
8138             DisplayInformation(bookOutput);
8139             bookRequested = FALSE;
8140             /* Fall through to parse the current output */
8141         }
8142     }
8143
8144     /*
8145      * Look for machine move.
8146      */
8147     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8148         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8149     {
8150         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8151             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8152             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8153             stalledEngine = cps;
8154             if(appData.ponderNextMove) { // bring opponent out of ponder
8155                 if(gameMode == TwoMachinesPlay) {
8156                     if(cps->other->pause)
8157                         PauseEngine(cps->other);
8158                     else
8159                         SendToProgram("easy\n", cps->other);
8160                 }
8161             }
8162             StopClocks();
8163             return;
8164         }
8165
8166         /* This method is only useful on engines that support ping */
8167         if (cps->lastPing != cps->lastPong) {
8168           if (gameMode == BeginningOfGame) {
8169             /* Extra move from before last new; ignore */
8170             if (appData.debugMode) {
8171                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8172             }
8173           } else {
8174             if (appData.debugMode) {
8175                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8176                         cps->which, gameMode);
8177             }
8178
8179             SendToProgram("undo\n", cps);
8180           }
8181           return;
8182         }
8183
8184         switch (gameMode) {
8185           case BeginningOfGame:
8186             /* Extra move from before last reset; ignore */
8187             if (appData.debugMode) {
8188                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8189             }
8190             return;
8191
8192           case EndOfGame:
8193           case IcsIdle:
8194           default:
8195             /* Extra move after we tried to stop.  The mode test is
8196                not a reliable way of detecting this problem, but it's
8197                the best we can do on engines that don't support ping.
8198             */
8199             if (appData.debugMode) {
8200                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8201                         cps->which, gameMode);
8202             }
8203             SendToProgram("undo\n", cps);
8204             return;
8205
8206           case MachinePlaysWhite:
8207           case IcsPlayingWhite:
8208             machineWhite = TRUE;
8209             break;
8210
8211           case MachinePlaysBlack:
8212           case IcsPlayingBlack:
8213             machineWhite = FALSE;
8214             break;
8215
8216           case TwoMachinesPlay:
8217             machineWhite = (cps->twoMachinesColor[0] == 'w');
8218             break;
8219         }
8220         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8221             if (appData.debugMode) {
8222                 fprintf(debugFP,
8223                         "Ignoring move out of turn by %s, gameMode %d"
8224                         ", forwardMost %d\n",
8225                         cps->which, gameMode, forwardMostMove);
8226             }
8227             return;
8228         }
8229
8230         if(cps->alphaRank) AlphaRank(machineMove, 4);
8231         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8232                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8233             /* Machine move could not be parsed; ignore it. */
8234           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8235                     machineMove, _(cps->which));
8236             DisplayMoveError(buf1);
8237             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8238                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8239             if (gameMode == TwoMachinesPlay) {
8240               GameEnds(machineWhite ? BlackWins : WhiteWins,
8241                        buf1, GE_XBOARD);
8242             }
8243             return;
8244         }
8245
8246         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8247         /* So we have to redo legality test with true e.p. status here,  */
8248         /* to make sure an illegal e.p. capture does not slip through,   */
8249         /* to cause a forfeit on a justified illegal-move complaint      */
8250         /* of the opponent.                                              */
8251         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8252            ChessMove moveType;
8253            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8254                              fromY, fromX, toY, toX, promoChar);
8255             if(moveType == IllegalMove) {
8256               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8257                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8258                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8259                            buf1, GE_XBOARD);
8260                 return;
8261            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8262            /* [HGM] Kludge to handle engines that send FRC-style castling
8263               when they shouldn't (like TSCP-Gothic) */
8264            switch(moveType) {
8265              case WhiteASideCastleFR:
8266              case BlackASideCastleFR:
8267                toX+=2;
8268                currentMoveString[2]++;
8269                break;
8270              case WhiteHSideCastleFR:
8271              case BlackHSideCastleFR:
8272                toX--;
8273                currentMoveString[2]--;
8274                break;
8275              default: ; // nothing to do, but suppresses warning of pedantic compilers
8276            }
8277         }
8278         hintRequested = FALSE;
8279         lastHint[0] = NULLCHAR;
8280         bookRequested = FALSE;
8281         /* Program may be pondering now */
8282         cps->maybeThinking = TRUE;
8283         if (cps->sendTime == 2) cps->sendTime = 1;
8284         if (cps->offeredDraw) cps->offeredDraw--;
8285
8286         /* [AS] Save move info*/
8287         pvInfoList[ forwardMostMove ].score = programStats.score;
8288         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8289         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8290
8291         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8292
8293         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8294         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8295             int count = 0;
8296
8297             while( count < adjudicateLossPlies ) {
8298                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8299
8300                 if( count & 1 ) {
8301                     score = -score; /* Flip score for winning side */
8302                 }
8303
8304                 if( score > adjudicateLossThreshold ) {
8305                     break;
8306                 }
8307
8308                 count++;
8309             }
8310
8311             if( count >= adjudicateLossPlies ) {
8312                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8313
8314                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8315                     "Xboard adjudication",
8316                     GE_XBOARD );
8317
8318                 return;
8319             }
8320         }
8321
8322         if(Adjudicate(cps)) {
8323             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8324             return; // [HGM] adjudicate: for all automatic game ends
8325         }
8326
8327 #if ZIPPY
8328         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8329             first.initDone) {
8330           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8331                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8332                 SendToICS("draw ");
8333                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8334           }
8335           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8336           ics_user_moved = 1;
8337           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8338                 char buf[3*MSG_SIZ];
8339
8340                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8341                         programStats.score / 100.,
8342                         programStats.depth,
8343                         programStats.time / 100.,
8344                         (unsigned int)programStats.nodes,
8345                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8346                         programStats.movelist);
8347                 SendToICS(buf);
8348 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8349           }
8350         }
8351 #endif
8352
8353         /* [AS] Clear stats for next move */
8354         ClearProgramStats();
8355         thinkOutput[0] = NULLCHAR;
8356         hiddenThinkOutputState = 0;
8357
8358         bookHit = NULL;
8359         if (gameMode == TwoMachinesPlay) {
8360             /* [HGM] relaying draw offers moved to after reception of move */
8361             /* and interpreting offer as claim if it brings draw condition */
8362             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8363                 SendToProgram("draw\n", cps->other);
8364             }
8365             if (cps->other->sendTime) {
8366                 SendTimeRemaining(cps->other,
8367                                   cps->other->twoMachinesColor[0] == 'w');
8368             }
8369             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8370             if (firstMove && !bookHit) {
8371                 firstMove = FALSE;
8372                 if (cps->other->useColors) {
8373                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8374                 }
8375                 SendToProgram("go\n", cps->other);
8376             }
8377             cps->other->maybeThinking = TRUE;
8378         }
8379
8380         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8381
8382         if (!pausing && appData.ringBellAfterMoves) {
8383             RingBell();
8384         }
8385
8386         /*
8387          * Reenable menu items that were disabled while
8388          * machine was thinking
8389          */
8390         if (gameMode != TwoMachinesPlay)
8391             SetUserThinkingEnables();
8392
8393         // [HGM] book: after book hit opponent has received move and is now in force mode
8394         // force the book reply into it, and then fake that it outputted this move by jumping
8395         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8396         if(bookHit) {
8397                 static char bookMove[MSG_SIZ]; // a bit generous?
8398
8399                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8400                 strcat(bookMove, bookHit);
8401                 message = bookMove;
8402                 cps = cps->other;
8403                 programStats.nodes = programStats.depth = programStats.time =
8404                 programStats.score = programStats.got_only_move = 0;
8405                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8406
8407                 if(cps->lastPing != cps->lastPong) {
8408                     savedMessage = message; // args for deferred call
8409                     savedState = cps;
8410                     ScheduleDelayedEvent(DeferredBookMove, 10);
8411                     return;
8412                 }
8413                 goto FakeBookMove;
8414         }
8415
8416         return;
8417     }
8418
8419     /* Set special modes for chess engines.  Later something general
8420      *  could be added here; for now there is just one kludge feature,
8421      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8422      *  when "xboard" is given as an interactive command.
8423      */
8424     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8425         cps->useSigint = FALSE;
8426         cps->useSigterm = FALSE;
8427     }
8428     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8429       ParseFeatures(message+8, cps);
8430       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8431     }
8432
8433     if (!strncmp(message, "setup ", 6) && 
8434         (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
8435                                         ) { // [HGM] allow first engine to define opening position
8436       int dummy, s=6; char buf[MSG_SIZ];
8437       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8438       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8439       if(startedFromSetupPosition) return;
8440       if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
8441       ParseFEN(boards[0], &dummy, message+s);
8442       DrawPosition(TRUE, boards[0]);
8443       startedFromSetupPosition = TRUE;
8444       return;
8445     }
8446     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8447      * want this, I was asked to put it in, and obliged.
8448      */
8449     if (!strncmp(message, "setboard ", 9)) {
8450         Board initial_position;
8451
8452         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8453
8454         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8455             DisplayError(_("Bad FEN received from engine"), 0);
8456             return ;
8457         } else {
8458            Reset(TRUE, FALSE);
8459            CopyBoard(boards[0], initial_position);
8460            initialRulePlies = FENrulePlies;
8461            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8462            else gameMode = MachinePlaysBlack;
8463            DrawPosition(FALSE, boards[currentMove]);
8464         }
8465         return;
8466     }
8467
8468     /*
8469      * Look for communication commands
8470      */
8471     if (!strncmp(message, "telluser ", 9)) {
8472         if(message[9] == '\\' && message[10] == '\\')
8473             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8474         PlayTellSound();
8475         DisplayNote(message + 9);
8476         return;
8477     }
8478     if (!strncmp(message, "tellusererror ", 14)) {
8479         cps->userError = 1;
8480         if(message[14] == '\\' && message[15] == '\\')
8481             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8482         PlayTellSound();
8483         DisplayError(message + 14, 0);
8484         return;
8485     }
8486     if (!strncmp(message, "tellopponent ", 13)) {
8487       if (appData.icsActive) {
8488         if (loggedOn) {
8489           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8490           SendToICS(buf1);
8491         }
8492       } else {
8493         DisplayNote(message + 13);
8494       }
8495       return;
8496     }
8497     if (!strncmp(message, "tellothers ", 11)) {
8498       if (appData.icsActive) {
8499         if (loggedOn) {
8500           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8501           SendToICS(buf1);
8502         }
8503       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8504       return;
8505     }
8506     if (!strncmp(message, "tellall ", 8)) {
8507       if (appData.icsActive) {
8508         if (loggedOn) {
8509           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8510           SendToICS(buf1);
8511         }
8512       } else {
8513         DisplayNote(message + 8);
8514       }
8515       return;
8516     }
8517     if (strncmp(message, "warning", 7) == 0) {
8518         /* Undocumented feature, use tellusererror in new code */
8519         DisplayError(message, 0);
8520         return;
8521     }
8522     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8523         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8524         strcat(realname, " query");
8525         AskQuestion(realname, buf2, buf1, cps->pr);
8526         return;
8527     }
8528     /* Commands from the engine directly to ICS.  We don't allow these to be
8529      *  sent until we are logged on. Crafty kibitzes have been known to
8530      *  interfere with the login process.
8531      */
8532     if (loggedOn) {
8533         if (!strncmp(message, "tellics ", 8)) {
8534             SendToICS(message + 8);
8535             SendToICS("\n");
8536             return;
8537         }
8538         if (!strncmp(message, "tellicsnoalias ", 15)) {
8539             SendToICS(ics_prefix);
8540             SendToICS(message + 15);
8541             SendToICS("\n");
8542             return;
8543         }
8544         /* The following are for backward compatibility only */
8545         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8546             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8547             SendToICS(ics_prefix);
8548             SendToICS(message);
8549             SendToICS("\n");
8550             return;
8551         }
8552     }
8553     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8554         return;
8555     }
8556     /*
8557      * If the move is illegal, cancel it and redraw the board.
8558      * Also deal with other error cases.  Matching is rather loose
8559      * here to accommodate engines written before the spec.
8560      */
8561     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8562         strncmp(message, "Error", 5) == 0) {
8563         if (StrStr(message, "name") ||
8564             StrStr(message, "rating") || StrStr(message, "?") ||
8565             StrStr(message, "result") || StrStr(message, "board") ||
8566             StrStr(message, "bk") || StrStr(message, "computer") ||
8567             StrStr(message, "variant") || StrStr(message, "hint") ||
8568             StrStr(message, "random") || StrStr(message, "depth") ||
8569             StrStr(message, "accepted")) {
8570             return;
8571         }
8572         if (StrStr(message, "protover")) {
8573           /* Program is responding to input, so it's apparently done
8574              initializing, and this error message indicates it is
8575              protocol version 1.  So we don't need to wait any longer
8576              for it to initialize and send feature commands. */
8577           FeatureDone(cps, 1);
8578           cps->protocolVersion = 1;
8579           return;
8580         }
8581         cps->maybeThinking = FALSE;
8582
8583         if (StrStr(message, "draw")) {
8584             /* Program doesn't have "draw" command */
8585             cps->sendDrawOffers = 0;
8586             return;
8587         }
8588         if (cps->sendTime != 1 &&
8589             (StrStr(message, "time") || StrStr(message, "otim"))) {
8590           /* Program apparently doesn't have "time" or "otim" command */
8591           cps->sendTime = 0;
8592           return;
8593         }
8594         if (StrStr(message, "analyze")) {
8595             cps->analysisSupport = FALSE;
8596             cps->analyzing = FALSE;
8597 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8598             EditGameEvent(); // [HGM] try to preserve loaded game
8599             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8600             DisplayError(buf2, 0);
8601             return;
8602         }
8603         if (StrStr(message, "(no matching move)st")) {
8604           /* Special kludge for GNU Chess 4 only */
8605           cps->stKludge = TRUE;
8606           SendTimeControl(cps, movesPerSession, timeControl,
8607                           timeIncrement, appData.searchDepth,
8608                           searchTime);
8609           return;
8610         }
8611         if (StrStr(message, "(no matching move)sd")) {
8612           /* Special kludge for GNU Chess 4 only */
8613           cps->sdKludge = TRUE;
8614           SendTimeControl(cps, movesPerSession, timeControl,
8615                           timeIncrement, appData.searchDepth,
8616                           searchTime);
8617           return;
8618         }
8619         if (!StrStr(message, "llegal")) {
8620             return;
8621         }
8622         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8623             gameMode == IcsIdle) return;
8624         if (forwardMostMove <= backwardMostMove) return;
8625         if (pausing) PauseEvent();
8626       if(appData.forceIllegal) {
8627             // [HGM] illegal: machine refused move; force position after move into it
8628           SendToProgram("force\n", cps);
8629           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8630                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8631                 // when black is to move, while there might be nothing on a2 or black
8632                 // might already have the move. So send the board as if white has the move.
8633                 // But first we must change the stm of the engine, as it refused the last move
8634                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8635                 if(WhiteOnMove(forwardMostMove)) {
8636                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8637                     SendBoard(cps, forwardMostMove); // kludgeless board
8638                 } else {
8639                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8640                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8641                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8642                 }
8643           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8644             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8645                  gameMode == TwoMachinesPlay)
8646               SendToProgram("go\n", cps);
8647             return;
8648       } else
8649         if (gameMode == PlayFromGameFile) {
8650             /* Stop reading this game file */
8651             gameMode = EditGame;
8652             ModeHighlight();
8653         }
8654         /* [HGM] illegal-move claim should forfeit game when Xboard */
8655         /* only passes fully legal moves                            */
8656         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8657             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8658                                 "False illegal-move claim", GE_XBOARD );
8659             return; // do not take back move we tested as valid
8660         }
8661         currentMove = forwardMostMove-1;
8662         DisplayMove(currentMove-1); /* before DisplayMoveError */
8663         SwitchClocks(forwardMostMove-1); // [HGM] race
8664         DisplayBothClocks();
8665         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8666                 parseList[currentMove], _(cps->which));
8667         DisplayMoveError(buf1);
8668         DrawPosition(FALSE, boards[currentMove]);
8669
8670         SetUserThinkingEnables();
8671         return;
8672     }
8673     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8674         /* Program has a broken "time" command that
8675            outputs a string not ending in newline.
8676            Don't use it. */
8677         cps->sendTime = 0;
8678     }
8679
8680     /*
8681      * If chess program startup fails, exit with an error message.
8682      * Attempts to recover here are futile. [HGM] Well, we try anyway
8683      */
8684     if ((StrStr(message, "unknown host") != NULL)
8685         || (StrStr(message, "No remote directory") != NULL)
8686         || (StrStr(message, "not found") != NULL)
8687         || (StrStr(message, "No such file") != NULL)
8688         || (StrStr(message, "can't alloc") != NULL)
8689         || (StrStr(message, "Permission denied") != NULL)) {
8690
8691         cps->maybeThinking = FALSE;
8692         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8693                 _(cps->which), cps->program, cps->host, message);
8694         RemoveInputSource(cps->isr);
8695         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8696             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8697             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8698         }
8699         return;
8700     }
8701
8702     /*
8703      * Look for hint output
8704      */
8705     if (sscanf(message, "Hint: %s", buf1) == 1) {
8706         if (cps == &first && hintRequested) {
8707             hintRequested = FALSE;
8708             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8709                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8710                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8711                                     PosFlags(forwardMostMove),
8712                                     fromY, fromX, toY, toX, promoChar, buf1);
8713                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8714                 DisplayInformation(buf2);
8715             } else {
8716                 /* Hint move could not be parsed!? */
8717               snprintf(buf2, sizeof(buf2),
8718                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8719                         buf1, _(cps->which));
8720                 DisplayError(buf2, 0);
8721             }
8722         } else {
8723           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8724         }
8725         return;
8726     }
8727
8728     /*
8729      * Ignore other messages if game is not in progress
8730      */
8731     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8732         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8733
8734     /*
8735      * look for win, lose, draw, or draw offer
8736      */
8737     if (strncmp(message, "1-0", 3) == 0) {
8738         char *p, *q, *r = "";
8739         p = strchr(message, '{');
8740         if (p) {
8741             q = strchr(p, '}');
8742             if (q) {
8743                 *q = NULLCHAR;
8744                 r = p + 1;
8745             }
8746         }
8747         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8748         return;
8749     } else if (strncmp(message, "0-1", 3) == 0) {
8750         char *p, *q, *r = "";
8751         p = strchr(message, '{');
8752         if (p) {
8753             q = strchr(p, '}');
8754             if (q) {
8755                 *q = NULLCHAR;
8756                 r = p + 1;
8757             }
8758         }
8759         /* Kludge for Arasan 4.1 bug */
8760         if (strcmp(r, "Black resigns") == 0) {
8761             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8762             return;
8763         }
8764         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8765         return;
8766     } else if (strncmp(message, "1/2", 3) == 0) {
8767         char *p, *q, *r = "";
8768         p = strchr(message, '{');
8769         if (p) {
8770             q = strchr(p, '}');
8771             if (q) {
8772                 *q = NULLCHAR;
8773                 r = p + 1;
8774             }
8775         }
8776
8777         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8778         return;
8779
8780     } else if (strncmp(message, "White resign", 12) == 0) {
8781         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8782         return;
8783     } else if (strncmp(message, "Black resign", 12) == 0) {
8784         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8785         return;
8786     } else if (strncmp(message, "White matches", 13) == 0 ||
8787                strncmp(message, "Black matches", 13) == 0   ) {
8788         /* [HGM] ignore GNUShogi noises */
8789         return;
8790     } else if (strncmp(message, "White", 5) == 0 &&
8791                message[5] != '(' &&
8792                StrStr(message, "Black") == NULL) {
8793         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8794         return;
8795     } else if (strncmp(message, "Black", 5) == 0 &&
8796                message[5] != '(') {
8797         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8798         return;
8799     } else if (strcmp(message, "resign") == 0 ||
8800                strcmp(message, "computer resigns") == 0) {
8801         switch (gameMode) {
8802           case MachinePlaysBlack:
8803           case IcsPlayingBlack:
8804             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8805             break;
8806           case MachinePlaysWhite:
8807           case IcsPlayingWhite:
8808             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8809             break;
8810           case TwoMachinesPlay:
8811             if (cps->twoMachinesColor[0] == 'w')
8812               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8813             else
8814               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8815             break;
8816           default:
8817             /* can't happen */
8818             break;
8819         }
8820         return;
8821     } else if (strncmp(message, "opponent mates", 14) == 0) {
8822         switch (gameMode) {
8823           case MachinePlaysBlack:
8824           case IcsPlayingBlack:
8825             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8826             break;
8827           case MachinePlaysWhite:
8828           case IcsPlayingWhite:
8829             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8830             break;
8831           case TwoMachinesPlay:
8832             if (cps->twoMachinesColor[0] == 'w')
8833               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8834             else
8835               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8836             break;
8837           default:
8838             /* can't happen */
8839             break;
8840         }
8841         return;
8842     } else if (strncmp(message, "computer mates", 14) == 0) {
8843         switch (gameMode) {
8844           case MachinePlaysBlack:
8845           case IcsPlayingBlack:
8846             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8847             break;
8848           case MachinePlaysWhite:
8849           case IcsPlayingWhite:
8850             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8851             break;
8852           case TwoMachinesPlay:
8853             if (cps->twoMachinesColor[0] == 'w')
8854               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8855             else
8856               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8857             break;
8858           default:
8859             /* can't happen */
8860             break;
8861         }
8862         return;
8863     } else if (strncmp(message, "checkmate", 9) == 0) {
8864         if (WhiteOnMove(forwardMostMove)) {
8865             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8866         } else {
8867             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8868         }
8869         return;
8870     } else if (strstr(message, "Draw") != NULL ||
8871                strstr(message, "game is a draw") != NULL) {
8872         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8873         return;
8874     } else if (strstr(message, "offer") != NULL &&
8875                strstr(message, "draw") != NULL) {
8876 #if ZIPPY
8877         if (appData.zippyPlay && first.initDone) {
8878             /* Relay offer to ICS */
8879             SendToICS(ics_prefix);
8880             SendToICS("draw\n");
8881         }
8882 #endif
8883         cps->offeredDraw = 2; /* valid until this engine moves twice */
8884         if (gameMode == TwoMachinesPlay) {
8885             if (cps->other->offeredDraw) {
8886                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8887             /* [HGM] in two-machine mode we delay relaying draw offer      */
8888             /* until after we also have move, to see if it is really claim */
8889             }
8890         } else if (gameMode == MachinePlaysWhite ||
8891                    gameMode == MachinePlaysBlack) {
8892           if (userOfferedDraw) {
8893             DisplayInformation(_("Machine accepts your draw offer"));
8894             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8895           } else {
8896             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8897           }
8898         }
8899     }
8900
8901
8902     /*
8903      * Look for thinking output
8904      */
8905     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8906           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8907                                 ) {
8908         int plylev, mvleft, mvtot, curscore, time;
8909         char mvname[MOVE_LEN];
8910         u64 nodes; // [DM]
8911         char plyext;
8912         int ignore = FALSE;
8913         int prefixHint = FALSE;
8914         mvname[0] = NULLCHAR;
8915
8916         switch (gameMode) {
8917           case MachinePlaysBlack:
8918           case IcsPlayingBlack:
8919             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8920             break;
8921           case MachinePlaysWhite:
8922           case IcsPlayingWhite:
8923             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8924             break;
8925           case AnalyzeMode:
8926           case AnalyzeFile:
8927             break;
8928           case IcsObserving: /* [DM] icsEngineAnalyze */
8929             if (!appData.icsEngineAnalyze) ignore = TRUE;
8930             break;
8931           case TwoMachinesPlay:
8932             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8933                 ignore = TRUE;
8934             }
8935             break;
8936           default:
8937             ignore = TRUE;
8938             break;
8939         }
8940
8941         if (!ignore) {
8942             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8943             buf1[0] = NULLCHAR;
8944             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8945                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8946
8947                 if (plyext != ' ' && plyext != '\t') {
8948                     time *= 100;
8949                 }
8950
8951                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8952                 if( cps->scoreIsAbsolute &&
8953                     ( gameMode == MachinePlaysBlack ||
8954                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8955                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8956                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8957                      !WhiteOnMove(currentMove)
8958                     ) )
8959                 {
8960                     curscore = -curscore;
8961                 }
8962
8963                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8964
8965                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8966                         char buf[MSG_SIZ];
8967                         FILE *f;
8968                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8969                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8970                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8971                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8972                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8973                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8974                                 fclose(f);
8975                         } else DisplayError(_("failed writing PV"), 0);
8976                 }
8977
8978                 tempStats.depth = plylev;
8979                 tempStats.nodes = nodes;
8980                 tempStats.time = time;
8981                 tempStats.score = curscore;
8982                 tempStats.got_only_move = 0;
8983
8984                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8985                         int ticklen;
8986
8987                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8988                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8989                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8990                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8991                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8992                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8993                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8994                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8995                 }
8996
8997                 /* Buffer overflow protection */
8998                 if (pv[0] != NULLCHAR) {
8999                     if (strlen(pv) >= sizeof(tempStats.movelist)
9000                         && appData.debugMode) {
9001                         fprintf(debugFP,
9002                                 "PV is too long; using the first %u bytes.\n",
9003                                 (unsigned) sizeof(tempStats.movelist) - 1);
9004                     }
9005
9006                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9007                 } else {
9008                     sprintf(tempStats.movelist, " no PV\n");
9009                 }
9010
9011                 if (tempStats.seen_stat) {
9012                     tempStats.ok_to_send = 1;
9013                 }
9014
9015                 if (strchr(tempStats.movelist, '(') != NULL) {
9016                     tempStats.line_is_book = 1;
9017                     tempStats.nr_moves = 0;
9018                     tempStats.moves_left = 0;
9019                 } else {
9020                     tempStats.line_is_book = 0;
9021                 }
9022
9023                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9024                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9025
9026                 SendProgramStatsToFrontend( cps, &tempStats );
9027
9028                 /*
9029                     [AS] Protect the thinkOutput buffer from overflow... this
9030                     is only useful if buf1 hasn't overflowed first!
9031                 */
9032                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9033                          plylev,
9034                          (gameMode == TwoMachinesPlay ?
9035                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9036                          ((double) curscore) / 100.0,
9037                          prefixHint ? lastHint : "",
9038                          prefixHint ? " " : "" );
9039
9040                 if( buf1[0] != NULLCHAR ) {
9041                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9042
9043                     if( strlen(pv) > max_len ) {
9044                         if( appData.debugMode) {
9045                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9046                         }
9047                         pv[max_len+1] = '\0';
9048                     }
9049
9050                     strcat( thinkOutput, pv);
9051                 }
9052
9053                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9054                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9055                     DisplayMove(currentMove - 1);
9056                 }
9057                 return;
9058
9059             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9060                 /* crafty (9.25+) says "(only move) <move>"
9061                  * if there is only 1 legal move
9062                  */
9063                 sscanf(p, "(only move) %s", buf1);
9064                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9065                 sprintf(programStats.movelist, "%s (only move)", buf1);
9066                 programStats.depth = 1;
9067                 programStats.nr_moves = 1;
9068                 programStats.moves_left = 1;
9069                 programStats.nodes = 1;
9070                 programStats.time = 1;
9071                 programStats.got_only_move = 1;
9072
9073                 /* Not really, but we also use this member to
9074                    mean "line isn't going to change" (Crafty
9075                    isn't searching, so stats won't change) */
9076                 programStats.line_is_book = 1;
9077
9078                 SendProgramStatsToFrontend( cps, &programStats );
9079
9080                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9081                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9082                     DisplayMove(currentMove - 1);
9083                 }
9084                 return;
9085             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9086                               &time, &nodes, &plylev, &mvleft,
9087                               &mvtot, mvname) >= 5) {
9088                 /* The stat01: line is from Crafty (9.29+) in response
9089                    to the "." command */
9090                 programStats.seen_stat = 1;
9091                 cps->maybeThinking = TRUE;
9092
9093                 if (programStats.got_only_move || !appData.periodicUpdates)
9094                   return;
9095
9096                 programStats.depth = plylev;
9097                 programStats.time = time;
9098                 programStats.nodes = nodes;
9099                 programStats.moves_left = mvleft;
9100                 programStats.nr_moves = mvtot;
9101                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9102                 programStats.ok_to_send = 1;
9103                 programStats.movelist[0] = '\0';
9104
9105                 SendProgramStatsToFrontend( cps, &programStats );
9106
9107                 return;
9108
9109             } else if (strncmp(message,"++",2) == 0) {
9110                 /* Crafty 9.29+ outputs this */
9111                 programStats.got_fail = 2;
9112                 return;
9113
9114             } else if (strncmp(message,"--",2) == 0) {
9115                 /* Crafty 9.29+ outputs this */
9116                 programStats.got_fail = 1;
9117                 return;
9118
9119             } else if (thinkOutput[0] != NULLCHAR &&
9120                        strncmp(message, "    ", 4) == 0) {
9121                 unsigned message_len;
9122
9123                 p = message;
9124                 while (*p && *p == ' ') p++;
9125
9126                 message_len = strlen( p );
9127
9128                 /* [AS] Avoid buffer overflow */
9129                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9130                     strcat(thinkOutput, " ");
9131                     strcat(thinkOutput, p);
9132                 }
9133
9134                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9135                     strcat(programStats.movelist, " ");
9136                     strcat(programStats.movelist, p);
9137                 }
9138
9139                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9140                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9141                     DisplayMove(currentMove - 1);
9142                 }
9143                 return;
9144             }
9145         }
9146         else {
9147             buf1[0] = NULLCHAR;
9148
9149             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9150                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9151             {
9152                 ChessProgramStats cpstats;
9153
9154                 if (plyext != ' ' && plyext != '\t') {
9155                     time *= 100;
9156                 }
9157
9158                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9159                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9160                     curscore = -curscore;
9161                 }
9162
9163                 cpstats.depth = plylev;
9164                 cpstats.nodes = nodes;
9165                 cpstats.time = time;
9166                 cpstats.score = curscore;
9167                 cpstats.got_only_move = 0;
9168                 cpstats.movelist[0] = '\0';
9169
9170                 if (buf1[0] != NULLCHAR) {
9171                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9172                 }
9173
9174                 cpstats.ok_to_send = 0;
9175                 cpstats.line_is_book = 0;
9176                 cpstats.nr_moves = 0;
9177                 cpstats.moves_left = 0;
9178
9179                 SendProgramStatsToFrontend( cps, &cpstats );
9180             }
9181         }
9182     }
9183 }
9184
9185
9186 /* Parse a game score from the character string "game", and
9187    record it as the history of the current game.  The game
9188    score is NOT assumed to start from the standard position.
9189    The display is not updated in any way.
9190    */
9191 void
9192 ParseGameHistory (char *game)
9193 {
9194     ChessMove moveType;
9195     int fromX, fromY, toX, toY, boardIndex;
9196     char promoChar;
9197     char *p, *q;
9198     char buf[MSG_SIZ];
9199
9200     if (appData.debugMode)
9201       fprintf(debugFP, "Parsing game history: %s\n", game);
9202
9203     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9204     gameInfo.site = StrSave(appData.icsHost);
9205     gameInfo.date = PGNDate();
9206     gameInfo.round = StrSave("-");
9207
9208     /* Parse out names of players */
9209     while (*game == ' ') game++;
9210     p = buf;
9211     while (*game != ' ') *p++ = *game++;
9212     *p = NULLCHAR;
9213     gameInfo.white = StrSave(buf);
9214     while (*game == ' ') game++;
9215     p = buf;
9216     while (*game != ' ' && *game != '\n') *p++ = *game++;
9217     *p = NULLCHAR;
9218     gameInfo.black = StrSave(buf);
9219
9220     /* Parse moves */
9221     boardIndex = blackPlaysFirst ? 1 : 0;
9222     yynewstr(game);
9223     for (;;) {
9224         yyboardindex = boardIndex;
9225         moveType = (ChessMove) Myylex();
9226         switch (moveType) {
9227           case IllegalMove:             /* maybe suicide chess, etc. */
9228   if (appData.debugMode) {
9229     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9230     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9231     setbuf(debugFP, NULL);
9232   }
9233           case WhitePromotion:
9234           case BlackPromotion:
9235           case WhiteNonPromotion:
9236           case BlackNonPromotion:
9237           case NormalMove:
9238           case WhiteCapturesEnPassant:
9239           case BlackCapturesEnPassant:
9240           case WhiteKingSideCastle:
9241           case WhiteQueenSideCastle:
9242           case BlackKingSideCastle:
9243           case BlackQueenSideCastle:
9244           case WhiteKingSideCastleWild:
9245           case WhiteQueenSideCastleWild:
9246           case BlackKingSideCastleWild:
9247           case BlackQueenSideCastleWild:
9248           /* PUSH Fabien */
9249           case WhiteHSideCastleFR:
9250           case WhiteASideCastleFR:
9251           case BlackHSideCastleFR:
9252           case BlackASideCastleFR:
9253           /* POP Fabien */
9254             fromX = currentMoveString[0] - AAA;
9255             fromY = currentMoveString[1] - ONE;
9256             toX = currentMoveString[2] - AAA;
9257             toY = currentMoveString[3] - ONE;
9258             promoChar = currentMoveString[4];
9259             break;
9260           case WhiteDrop:
9261           case BlackDrop:
9262             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9263             fromX = moveType == WhiteDrop ?
9264               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9265             (int) CharToPiece(ToLower(currentMoveString[0]));
9266             fromY = DROP_RANK;
9267             toX = currentMoveString[2] - AAA;
9268             toY = currentMoveString[3] - ONE;
9269             promoChar = NULLCHAR;
9270             break;
9271           case AmbiguousMove:
9272             /* bug? */
9273             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9274   if (appData.debugMode) {
9275     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9276     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9277     setbuf(debugFP, NULL);
9278   }
9279             DisplayError(buf, 0);
9280             return;
9281           case ImpossibleMove:
9282             /* bug? */
9283             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9284   if (appData.debugMode) {
9285     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9286     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9287     setbuf(debugFP, NULL);
9288   }
9289             DisplayError(buf, 0);
9290             return;
9291           case EndOfFile:
9292             if (boardIndex < backwardMostMove) {
9293                 /* Oops, gap.  How did that happen? */
9294                 DisplayError(_("Gap in move list"), 0);
9295                 return;
9296             }
9297             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9298             if (boardIndex > forwardMostMove) {
9299                 forwardMostMove = boardIndex;
9300             }
9301             return;
9302           case ElapsedTime:
9303             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9304                 strcat(parseList[boardIndex-1], " ");
9305                 strcat(parseList[boardIndex-1], yy_text);
9306             }
9307             continue;
9308           case Comment:
9309           case PGNTag:
9310           case NAG:
9311           default:
9312             /* ignore */
9313             continue;
9314           case WhiteWins:
9315           case BlackWins:
9316           case GameIsDrawn:
9317           case GameUnfinished:
9318             if (gameMode == IcsExamining) {
9319                 if (boardIndex < backwardMostMove) {
9320                     /* Oops, gap.  How did that happen? */
9321                     return;
9322                 }
9323                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9324                 return;
9325             }
9326             gameInfo.result = moveType;
9327             p = strchr(yy_text, '{');
9328             if (p == NULL) p = strchr(yy_text, '(');
9329             if (p == NULL) {
9330                 p = yy_text;
9331                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9332             } else {
9333                 q = strchr(p, *p == '{' ? '}' : ')');
9334                 if (q != NULL) *q = NULLCHAR;
9335                 p++;
9336             }
9337             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9338             gameInfo.resultDetails = StrSave(p);
9339             continue;
9340         }
9341         if (boardIndex >= forwardMostMove &&
9342             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9343             backwardMostMove = blackPlaysFirst ? 1 : 0;
9344             return;
9345         }
9346         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9347                                  fromY, fromX, toY, toX, promoChar,
9348                                  parseList[boardIndex]);
9349         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9350         /* currentMoveString is set as a side-effect of yylex */
9351         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9352         strcat(moveList[boardIndex], "\n");
9353         boardIndex++;
9354         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9355         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9356           case MT_NONE:
9357           case MT_STALEMATE:
9358           default:
9359             break;
9360           case MT_CHECK:
9361             if(gameInfo.variant != VariantShogi)
9362                 strcat(parseList[boardIndex - 1], "+");
9363             break;
9364           case MT_CHECKMATE:
9365           case MT_STAINMATE:
9366             strcat(parseList[boardIndex - 1], "#");
9367             break;
9368         }
9369     }
9370 }
9371
9372
9373 /* Apply a move to the given board  */
9374 void
9375 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9376 {
9377   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9378   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9379
9380     /* [HGM] compute & store e.p. status and castling rights for new position */
9381     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9382
9383       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9384       oldEP = (signed char)board[EP_STATUS];
9385       board[EP_STATUS] = EP_NONE;
9386
9387   if (fromY == DROP_RANK) {
9388         /* must be first */
9389         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9390             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9391             return;
9392         }
9393         piece = board[toY][toX] = (ChessSquare) fromX;
9394   } else {
9395       int i;
9396
9397       if( board[toY][toX] != EmptySquare )
9398            board[EP_STATUS] = EP_CAPTURE;
9399
9400       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9401            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9402                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9403       } else
9404       if( board[fromY][fromX] == WhitePawn ) {
9405            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9406                board[EP_STATUS] = EP_PAWN_MOVE;
9407            if( toY-fromY==2) {
9408                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9409                         gameInfo.variant != VariantBerolina || toX < fromX)
9410                       board[EP_STATUS] = toX | berolina;
9411                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9412                         gameInfo.variant != VariantBerolina || toX > fromX)
9413                       board[EP_STATUS] = toX;
9414            }
9415       } else
9416       if( board[fromY][fromX] == BlackPawn ) {
9417            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9418                board[EP_STATUS] = EP_PAWN_MOVE;
9419            if( toY-fromY== -2) {
9420                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9421                         gameInfo.variant != VariantBerolina || toX < fromX)
9422                       board[EP_STATUS] = toX | berolina;
9423                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9424                         gameInfo.variant != VariantBerolina || toX > fromX)
9425                       board[EP_STATUS] = toX;
9426            }
9427        }
9428
9429        for(i=0; i<nrCastlingRights; i++) {
9430            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9431               board[CASTLING][i] == toX   && castlingRank[i] == toY
9432              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9433        }
9434
9435        if(gameInfo.variant == VariantSChess) { // update virginity
9436            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9437            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9438            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9439            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9440        }
9441
9442      if (fromX == toX && fromY == toY) return;
9443
9444      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9445      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9446      if(gameInfo.variant == VariantKnightmate)
9447          king += (int) WhiteUnicorn - (int) WhiteKing;
9448
9449     /* Code added by Tord: */
9450     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9451     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9452         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9453       board[fromY][fromX] = EmptySquare;
9454       board[toY][toX] = EmptySquare;
9455       if((toX > fromX) != (piece == WhiteRook)) {
9456         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9457       } else {
9458         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9459       }
9460     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9461                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9462       board[fromY][fromX] = EmptySquare;
9463       board[toY][toX] = EmptySquare;
9464       if((toX > fromX) != (piece == BlackRook)) {
9465         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9466       } else {
9467         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9468       }
9469     /* End of code added by Tord */
9470
9471     } else if (board[fromY][fromX] == king
9472         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9473         && toY == fromY && toX > fromX+1) {
9474         board[fromY][fromX] = EmptySquare;
9475         board[toY][toX] = king;
9476         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9477         board[fromY][BOARD_RGHT-1] = EmptySquare;
9478     } else if (board[fromY][fromX] == king
9479         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9480                && toY == fromY && toX < fromX-1) {
9481         board[fromY][fromX] = EmptySquare;
9482         board[toY][toX] = king;
9483         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9484         board[fromY][BOARD_LEFT] = EmptySquare;
9485     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9486                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9487                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9488                ) {
9489         /* white pawn promotion */
9490         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9491         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9492             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9493         board[fromY][fromX] = EmptySquare;
9494     } else if ((fromY >= BOARD_HEIGHT>>1)
9495                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9496                && (toX != fromX)
9497                && gameInfo.variant != VariantXiangqi
9498                && gameInfo.variant != VariantBerolina
9499                && (board[fromY][fromX] == WhitePawn)
9500                && (board[toY][toX] == EmptySquare)) {
9501         board[fromY][fromX] = EmptySquare;
9502         board[toY][toX] = WhitePawn;
9503         captured = board[toY - 1][toX];
9504         board[toY - 1][toX] = EmptySquare;
9505     } else if ((fromY == BOARD_HEIGHT-4)
9506                && (toX == fromX)
9507                && gameInfo.variant == VariantBerolina
9508                && (board[fromY][fromX] == WhitePawn)
9509                && (board[toY][toX] == EmptySquare)) {
9510         board[fromY][fromX] = EmptySquare;
9511         board[toY][toX] = WhitePawn;
9512         if(oldEP & EP_BEROLIN_A) {
9513                 captured = board[fromY][fromX-1];
9514                 board[fromY][fromX-1] = EmptySquare;
9515         }else{  captured = board[fromY][fromX+1];
9516                 board[fromY][fromX+1] = EmptySquare;
9517         }
9518     } else if (board[fromY][fromX] == king
9519         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9520                && toY == fromY && toX > fromX+1) {
9521         board[fromY][fromX] = EmptySquare;
9522         board[toY][toX] = king;
9523         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9524         board[fromY][BOARD_RGHT-1] = EmptySquare;
9525     } else if (board[fromY][fromX] == king
9526         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9527                && toY == fromY && toX < fromX-1) {
9528         board[fromY][fromX] = EmptySquare;
9529         board[toY][toX] = king;
9530         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9531         board[fromY][BOARD_LEFT] = EmptySquare;
9532     } else if (fromY == 7 && fromX == 3
9533                && board[fromY][fromX] == BlackKing
9534                && toY == 7 && toX == 5) {
9535         board[fromY][fromX] = EmptySquare;
9536         board[toY][toX] = BlackKing;
9537         board[fromY][7] = EmptySquare;
9538         board[toY][4] = BlackRook;
9539     } else if (fromY == 7 && fromX == 3
9540                && board[fromY][fromX] == BlackKing
9541                && toY == 7 && toX == 1) {
9542         board[fromY][fromX] = EmptySquare;
9543         board[toY][toX] = BlackKing;
9544         board[fromY][0] = EmptySquare;
9545         board[toY][2] = BlackRook;
9546     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9547                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9548                && toY < promoRank && promoChar
9549                ) {
9550         /* black pawn promotion */
9551         board[toY][toX] = CharToPiece(ToLower(promoChar));
9552         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9553             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9554         board[fromY][fromX] = EmptySquare;
9555     } else if ((fromY < BOARD_HEIGHT>>1)
9556                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9557                && (toX != fromX)
9558                && gameInfo.variant != VariantXiangqi
9559                && gameInfo.variant != VariantBerolina
9560                && (board[fromY][fromX] == BlackPawn)
9561                && (board[toY][toX] == EmptySquare)) {
9562         board[fromY][fromX] = EmptySquare;
9563         board[toY][toX] = BlackPawn;
9564         captured = board[toY + 1][toX];
9565         board[toY + 1][toX] = EmptySquare;
9566     } else if ((fromY == 3)
9567                && (toX == fromX)
9568                && gameInfo.variant == VariantBerolina
9569                && (board[fromY][fromX] == BlackPawn)
9570                && (board[toY][toX] == EmptySquare)) {
9571         board[fromY][fromX] = EmptySquare;
9572         board[toY][toX] = BlackPawn;
9573         if(oldEP & EP_BEROLIN_A) {
9574                 captured = board[fromY][fromX-1];
9575                 board[fromY][fromX-1] = EmptySquare;
9576         }else{  captured = board[fromY][fromX+1];
9577                 board[fromY][fromX+1] = EmptySquare;
9578         }
9579     } else {
9580         board[toY][toX] = board[fromY][fromX];
9581         board[fromY][fromX] = EmptySquare;
9582     }
9583   }
9584
9585     if (gameInfo.holdingsWidth != 0) {
9586
9587       /* !!A lot more code needs to be written to support holdings  */
9588       /* [HGM] OK, so I have written it. Holdings are stored in the */
9589       /* penultimate board files, so they are automaticlly stored   */
9590       /* in the game history.                                       */
9591       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9592                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9593         /* Delete from holdings, by decreasing count */
9594         /* and erasing image if necessary            */
9595         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9596         if(p < (int) BlackPawn) { /* white drop */
9597              p -= (int)WhitePawn;
9598                  p = PieceToNumber((ChessSquare)p);
9599              if(p >= gameInfo.holdingsSize) p = 0;
9600              if(--board[p][BOARD_WIDTH-2] <= 0)
9601                   board[p][BOARD_WIDTH-1] = EmptySquare;
9602              if((int)board[p][BOARD_WIDTH-2] < 0)
9603                         board[p][BOARD_WIDTH-2] = 0;
9604         } else {                  /* black drop */
9605              p -= (int)BlackPawn;
9606                  p = PieceToNumber((ChessSquare)p);
9607              if(p >= gameInfo.holdingsSize) p = 0;
9608              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9609                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9610              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9611                         board[BOARD_HEIGHT-1-p][1] = 0;
9612         }
9613       }
9614       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9615           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9616         /* [HGM] holdings: Add to holdings, if holdings exist */
9617         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9618                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9619                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9620         }
9621         p = (int) captured;
9622         if (p >= (int) BlackPawn) {
9623           p -= (int)BlackPawn;
9624           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9625                   /* in Shogi restore piece to its original  first */
9626                   captured = (ChessSquare) (DEMOTED captured);
9627                   p = DEMOTED p;
9628           }
9629           p = PieceToNumber((ChessSquare)p);
9630           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9631           board[p][BOARD_WIDTH-2]++;
9632           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9633         } else {
9634           p -= (int)WhitePawn;
9635           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9636                   captured = (ChessSquare) (DEMOTED captured);
9637                   p = DEMOTED p;
9638           }
9639           p = PieceToNumber((ChessSquare)p);
9640           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9641           board[BOARD_HEIGHT-1-p][1]++;
9642           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9643         }
9644       }
9645     } else if (gameInfo.variant == VariantAtomic) {
9646       if (captured != EmptySquare) {
9647         int y, x;
9648         for (y = toY-1; y <= toY+1; y++) {
9649           for (x = toX-1; x <= toX+1; x++) {
9650             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9651                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9652               board[y][x] = EmptySquare;
9653             }
9654           }
9655         }
9656         board[toY][toX] = EmptySquare;
9657       }
9658     }
9659     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9660         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9661     } else
9662     if(promoChar == '+') {
9663         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9664         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9665     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9666         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9667         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9668            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9669         board[toY][toX] = newPiece;
9670     }
9671     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9672                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9673         // [HGM] superchess: take promotion piece out of holdings
9674         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9675         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9676             if(!--board[k][BOARD_WIDTH-2])
9677                 board[k][BOARD_WIDTH-1] = EmptySquare;
9678         } else {
9679             if(!--board[BOARD_HEIGHT-1-k][1])
9680                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9681         }
9682     }
9683
9684 }
9685
9686 /* Updates forwardMostMove */
9687 void
9688 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9689 {
9690 //    forwardMostMove++; // [HGM] bare: moved downstream
9691
9692     (void) CoordsToAlgebraic(boards[forwardMostMove],
9693                              PosFlags(forwardMostMove),
9694                              fromY, fromX, toY, toX, promoChar,
9695                              parseList[forwardMostMove]);
9696
9697     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9698         int timeLeft; static int lastLoadFlag=0; int king, piece;
9699         piece = boards[forwardMostMove][fromY][fromX];
9700         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9701         if(gameInfo.variant == VariantKnightmate)
9702             king += (int) WhiteUnicorn - (int) WhiteKing;
9703         if(forwardMostMove == 0) {
9704             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9705                 fprintf(serverMoves, "%s;", UserName());
9706             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9707                 fprintf(serverMoves, "%s;", second.tidy);
9708             fprintf(serverMoves, "%s;", first.tidy);
9709             if(gameMode == MachinePlaysWhite)
9710                 fprintf(serverMoves, "%s;", UserName());
9711             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9712                 fprintf(serverMoves, "%s;", second.tidy);
9713         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9714         lastLoadFlag = loadFlag;
9715         // print base move
9716         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9717         // print castling suffix
9718         if( toY == fromY && piece == king ) {
9719             if(toX-fromX > 1)
9720                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9721             if(fromX-toX >1)
9722                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9723         }
9724         // e.p. suffix
9725         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9726              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9727              boards[forwardMostMove][toY][toX] == EmptySquare
9728              && fromX != toX && fromY != toY)
9729                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9730         // promotion suffix
9731         if(promoChar != NULLCHAR) {
9732             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9733                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9734                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9735             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9736         }
9737         if(!loadFlag) {
9738                 char buf[MOVE_LEN*2], *p; int len;
9739             fprintf(serverMoves, "/%d/%d",
9740                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9741             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9742             else                      timeLeft = blackTimeRemaining/1000;
9743             fprintf(serverMoves, "/%d", timeLeft);
9744                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9745                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9746                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9747                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9748             fprintf(serverMoves, "/%s", buf);
9749         }
9750         fflush(serverMoves);
9751     }
9752
9753     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9754         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9755       return;
9756     }
9757     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9758     if (commentList[forwardMostMove+1] != NULL) {
9759         free(commentList[forwardMostMove+1]);
9760         commentList[forwardMostMove+1] = NULL;
9761     }
9762     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9763     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9764     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9765     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9766     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9767     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9768     adjustedClock = FALSE;
9769     gameInfo.result = GameUnfinished;
9770     if (gameInfo.resultDetails != NULL) {
9771         free(gameInfo.resultDetails);
9772         gameInfo.resultDetails = NULL;
9773     }
9774     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9775                               moveList[forwardMostMove - 1]);
9776     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9777       case MT_NONE:
9778       case MT_STALEMATE:
9779       default:
9780         break;
9781       case MT_CHECK:
9782         if(gameInfo.variant != VariantShogi)
9783             strcat(parseList[forwardMostMove - 1], "+");
9784         break;
9785       case MT_CHECKMATE:
9786       case MT_STAINMATE:
9787         strcat(parseList[forwardMostMove - 1], "#");
9788         break;
9789     }
9790
9791 }
9792
9793 /* Updates currentMove if not pausing */
9794 void
9795 ShowMove (int fromX, int fromY, int toX, int toY)
9796 {
9797     int instant = (gameMode == PlayFromGameFile) ?
9798         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9799     if(appData.noGUI) return;
9800     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9801         if (!instant) {
9802             if (forwardMostMove == currentMove + 1) {
9803                 AnimateMove(boards[forwardMostMove - 1],
9804                             fromX, fromY, toX, toY);
9805             }
9806         }
9807         currentMove = forwardMostMove;
9808     }
9809
9810     if (instant) return;
9811
9812     DisplayMove(currentMove - 1);
9813     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9814             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9815                 SetHighlights(fromX, fromY, toX, toY);
9816             }
9817     }
9818     DrawPosition(FALSE, boards[currentMove]);
9819     DisplayBothClocks();
9820     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9821 }
9822
9823 void
9824 SendEgtPath (ChessProgramState *cps)
9825 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9826         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9827
9828         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9829
9830         while(*p) {
9831             char c, *q = name+1, *r, *s;
9832
9833             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9834             while(*p && *p != ',') *q++ = *p++;
9835             *q++ = ':'; *q = 0;
9836             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9837                 strcmp(name, ",nalimov:") == 0 ) {
9838                 // take nalimov path from the menu-changeable option first, if it is defined
9839               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9840                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9841             } else
9842             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9843                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9844                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9845                 s = r = StrStr(s, ":") + 1; // beginning of path info
9846                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9847                 c = *r; *r = 0;             // temporarily null-terminate path info
9848                     *--q = 0;               // strip of trailig ':' from name
9849                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9850                 *r = c;
9851                 SendToProgram(buf,cps);     // send egtbpath command for this format
9852             }
9853             if(*p == ',') p++; // read away comma to position for next format name
9854         }
9855 }
9856
9857 static int
9858 NonStandardBoardSize ()
9859 {
9860       /* [HGM] Awkward testing. Should really be a table */
9861       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9862       if( gameInfo.variant == VariantXiangqi )
9863            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9864       if( gameInfo.variant == VariantShogi )
9865            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9866       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9867            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9868       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9869           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9870            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9871       if( gameInfo.variant == VariantCourier )
9872            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9873       if( gameInfo.variant == VariantSuper )
9874            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9875       if( gameInfo.variant == VariantGreat )
9876            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9877       if( gameInfo.variant == VariantSChess )
9878            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9879       if( gameInfo.variant == VariantGrand )
9880            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9881       return overruled;
9882 }
9883
9884 void
9885 InitChessProgram (ChessProgramState *cps, int setup)
9886 /* setup needed to setup FRC opening position */
9887 {
9888     char buf[MSG_SIZ], b[MSG_SIZ];
9889     if (appData.noChessProgram) return;
9890     hintRequested = FALSE;
9891     bookRequested = FALSE;
9892
9893     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9894     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9895     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9896     if(cps->memSize) { /* [HGM] memory */
9897       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9898         SendToProgram(buf, cps);
9899     }
9900     SendEgtPath(cps); /* [HGM] EGT */
9901     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9902       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9903         SendToProgram(buf, cps);
9904     }
9905
9906     SendToProgram(cps->initString, cps);
9907     if (gameInfo.variant != VariantNormal &&
9908         gameInfo.variant != VariantLoadable
9909         /* [HGM] also send variant if board size non-standard */
9910         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9911                                             ) {
9912       char *v = VariantName(gameInfo.variant);
9913       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9914         /* [HGM] in protocol 1 we have to assume all variants valid */
9915         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9916         DisplayFatalError(buf, 0, 1);
9917         return;
9918       }
9919
9920       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
9921         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9922                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9923            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9924            if(StrStr(cps->variants, b) == NULL) {
9925                // specific sized variant not known, check if general sizing allowed
9926                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9927                    if(StrStr(cps->variants, "boardsize") == NULL) {
9928                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9929                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9930                        DisplayFatalError(buf, 0, 1);
9931                        return;
9932                    }
9933                    /* [HGM] here we really should compare with the maximum supported board size */
9934                }
9935            }
9936       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9937       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9938       SendToProgram(buf, cps);
9939     }
9940     currentlyInitializedVariant = gameInfo.variant;
9941
9942     /* [HGM] send opening position in FRC to first engine */
9943     if(setup) {
9944           SendToProgram("force\n", cps);
9945           SendBoard(cps, 0);
9946           /* engine is now in force mode! Set flag to wake it up after first move. */
9947           setboardSpoiledMachineBlack = 1;
9948     }
9949
9950     if (cps->sendICS) {
9951       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9952       SendToProgram(buf, cps);
9953     }
9954     cps->maybeThinking = FALSE;
9955     cps->offeredDraw = 0;
9956     if (!appData.icsActive) {
9957         SendTimeControl(cps, movesPerSession, timeControl,
9958                         timeIncrement, appData.searchDepth,
9959                         searchTime);
9960     }
9961     if (appData.showThinking
9962         // [HGM] thinking: four options require thinking output to be sent
9963         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9964                                 ) {
9965         SendToProgram("post\n", cps);
9966     }
9967     SendToProgram("hard\n", cps);
9968     if (!appData.ponderNextMove) {
9969         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9970            it without being sure what state we are in first.  "hard"
9971            is not a toggle, so that one is OK.
9972          */
9973         SendToProgram("easy\n", cps);
9974     }
9975     if (cps->usePing) {
9976       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9977       SendToProgram(buf, cps);
9978     }
9979     cps->initDone = TRUE;
9980     ClearEngineOutputPane(cps == &second);
9981 }
9982
9983
9984 void
9985 ResendOptions (ChessProgramState *cps)
9986 { // send the stored value of the options
9987   int i;
9988   char buf[MSG_SIZ];
9989   Option *opt = cps->option;
9990   for(i=0; i<cps->nrOptions; i++, opt++) {
9991       switch(opt->type) {
9992         case Spin:
9993         case Slider:
9994         case CheckBox:
9995             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
9996           break;
9997         case ComboBox:
9998           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
9999           break;
10000         default:
10001             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10002           break;
10003         case Button:
10004         case SaveButton:
10005           continue;
10006       }
10007       SendToProgram(buf, cps);
10008   }
10009 }
10010
10011 void
10012 StartChessProgram (ChessProgramState *cps)
10013 {
10014     char buf[MSG_SIZ];
10015     int err;
10016
10017     if (appData.noChessProgram) return;
10018     cps->initDone = FALSE;
10019
10020     if (strcmp(cps->host, "localhost") == 0) {
10021         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10022     } else if (*appData.remoteShell == NULLCHAR) {
10023         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10024     } else {
10025         if (*appData.remoteUser == NULLCHAR) {
10026           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10027                     cps->program);
10028         } else {
10029           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10030                     cps->host, appData.remoteUser, cps->program);
10031         }
10032         err = StartChildProcess(buf, "", &cps->pr);
10033     }
10034
10035     if (err != 0) {
10036       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10037         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10038         if(cps != &first) return;
10039         appData.noChessProgram = TRUE;
10040         ThawUI();
10041         SetNCPMode();
10042 //      DisplayFatalError(buf, err, 1);
10043 //      cps->pr = NoProc;
10044 //      cps->isr = NULL;
10045         return;
10046     }
10047
10048     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10049     if (cps->protocolVersion > 1) {
10050       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10051       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10052         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10053         cps->comboCnt = 0;  //                and values of combo boxes
10054       }
10055       SendToProgram(buf, cps);
10056       if(cps->reload) ResendOptions(cps);
10057     } else {
10058       SendToProgram("xboard\n", cps);
10059     }
10060 }
10061
10062 void
10063 TwoMachinesEventIfReady P((void))
10064 {
10065   static int curMess = 0;
10066   if (first.lastPing != first.lastPong) {
10067     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10068     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10069     return;
10070   }
10071   if (second.lastPing != second.lastPong) {
10072     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10073     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10074     return;
10075   }
10076   DisplayMessage("", ""); curMess = 0;
10077   TwoMachinesEvent();
10078 }
10079
10080 char *
10081 MakeName (char *template)
10082 {
10083     time_t clock;
10084     struct tm *tm;
10085     static char buf[MSG_SIZ];
10086     char *p = buf;
10087     int i;
10088
10089     clock = time((time_t *)NULL);
10090     tm = localtime(&clock);
10091
10092     while(*p++ = *template++) if(p[-1] == '%') {
10093         switch(*template++) {
10094           case 0:   *p = 0; return buf;
10095           case 'Y': i = tm->tm_year+1900; break;
10096           case 'y': i = tm->tm_year-100; break;
10097           case 'M': i = tm->tm_mon+1; break;
10098           case 'd': i = tm->tm_mday; break;
10099           case 'h': i = tm->tm_hour; break;
10100           case 'm': i = tm->tm_min; break;
10101           case 's': i = tm->tm_sec; break;
10102           default:  i = 0;
10103         }
10104         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10105     }
10106     return buf;
10107 }
10108
10109 int
10110 CountPlayers (char *p)
10111 {
10112     int n = 0;
10113     while(p = strchr(p, '\n')) p++, n++; // count participants
10114     return n;
10115 }
10116
10117 FILE *
10118 WriteTourneyFile (char *results, FILE *f)
10119 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10120     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10121     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10122         // create a file with tournament description
10123         fprintf(f, "-participants {%s}\n", appData.participants);
10124         fprintf(f, "-seedBase %d\n", appData.seedBase);
10125         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10126         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10127         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10128         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10129         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10130         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10131         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10132         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10133         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10134         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10135         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10136         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10137         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10138         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10139         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10140         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10141         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10142         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10143         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10144         fprintf(f, "-smpCores %d\n", appData.smpCores);
10145         if(searchTime > 0)
10146                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10147         else {
10148                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10149                 fprintf(f, "-tc %s\n", appData.timeControl);
10150                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10151         }
10152         fprintf(f, "-results \"%s\"\n", results);
10153     }
10154     return f;
10155 }
10156
10157 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10158
10159 void
10160 Substitute (char *participants, int expunge)
10161 {
10162     int i, changed, changes=0, nPlayers=0;
10163     char *p, *q, *r, buf[MSG_SIZ];
10164     if(participants == NULL) return;
10165     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10166     r = p = participants; q = appData.participants;
10167     while(*p && *p == *q) {
10168         if(*p == '\n') r = p+1, nPlayers++;
10169         p++; q++;
10170     }
10171     if(*p) { // difference
10172         while(*p && *p++ != '\n');
10173         while(*q && *q++ != '\n');
10174       changed = nPlayers;
10175         changes = 1 + (strcmp(p, q) != 0);
10176     }
10177     if(changes == 1) { // a single engine mnemonic was changed
10178         q = r; while(*q) nPlayers += (*q++ == '\n');
10179         p = buf; while(*r && (*p = *r++) != '\n') p++;
10180         *p = NULLCHAR;
10181         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10182         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10183         if(mnemonic[i]) { // The substitute is valid
10184             FILE *f;
10185             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10186                 flock(fileno(f), LOCK_EX);
10187                 ParseArgsFromFile(f);
10188                 fseek(f, 0, SEEK_SET);
10189                 FREE(appData.participants); appData.participants = participants;
10190                 if(expunge) { // erase results of replaced engine
10191                     int len = strlen(appData.results), w, b, dummy;
10192                     for(i=0; i<len; i++) {
10193                         Pairing(i, nPlayers, &w, &b, &dummy);
10194                         if((w == changed || b == changed) && appData.results[i] == '*') {
10195                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10196                             fclose(f);
10197                             return;
10198                         }
10199                     }
10200                     for(i=0; i<len; i++) {
10201                         Pairing(i, nPlayers, &w, &b, &dummy);
10202                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10203                     }
10204                 }
10205                 WriteTourneyFile(appData.results, f);
10206                 fclose(f); // release lock
10207                 return;
10208             }
10209         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10210     }
10211     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10212     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10213     free(participants);
10214     return;
10215 }
10216
10217 int
10218 CheckPlayers (char *participants)
10219 {
10220         int i;
10221         char buf[MSG_SIZ], *p;
10222         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10223         while(p = strchr(participants, '\n')) {
10224             *p = NULLCHAR;
10225             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10226             if(!mnemonic[i]) {
10227                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10228                 *p = '\n';
10229                 DisplayError(buf, 0);
10230                 return 1;
10231             }
10232             *p = '\n';
10233             participants = p + 1;
10234         }
10235         return 0;
10236 }
10237
10238 int
10239 CreateTourney (char *name)
10240 {
10241         FILE *f;
10242         if(matchMode && strcmp(name, appData.tourneyFile)) {
10243              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10244         }
10245         if(name[0] == NULLCHAR) {
10246             if(appData.participants[0])
10247                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10248             return 0;
10249         }
10250         f = fopen(name, "r");
10251         if(f) { // file exists
10252             ASSIGN(appData.tourneyFile, name);
10253             ParseArgsFromFile(f); // parse it
10254         } else {
10255             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10256             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10257                 DisplayError(_("Not enough participants"), 0);
10258                 return 0;
10259             }
10260             if(CheckPlayers(appData.participants)) return 0;
10261             ASSIGN(appData.tourneyFile, name);
10262             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10263             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10264         }
10265         fclose(f);
10266         appData.noChessProgram = FALSE;
10267         appData.clockMode = TRUE;
10268         SetGNUMode();
10269         return 1;
10270 }
10271
10272 int
10273 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10274 {
10275     char buf[MSG_SIZ], *p, *q;
10276     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10277     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10278     skip = !all && group[0]; // if group requested, we start in skip mode
10279     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10280         p = names; q = buf; header = 0;
10281         while(*p && *p != '\n') *q++ = *p++;
10282         *q = 0;
10283         if(*p == '\n') p++;
10284         if(buf[0] == '#') {
10285             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10286             depth++; // we must be entering a new group
10287             if(all) continue; // suppress printing group headers when complete list requested
10288             header = 1;
10289             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10290         }
10291         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10292         if(engineList[i]) free(engineList[i]);
10293         engineList[i] = strdup(buf);
10294         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10295         if(engineMnemonic[i]) free(engineMnemonic[i]);
10296         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10297             strcat(buf, " (");
10298             sscanf(q + 8, "%s", buf + strlen(buf));
10299             strcat(buf, ")");
10300         }
10301         engineMnemonic[i] = strdup(buf);
10302         i++;
10303     }
10304     engineList[i] = engineMnemonic[i] = NULL;
10305     return i;
10306 }
10307
10308 // following implemented as macro to avoid type limitations
10309 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10310
10311 void
10312 SwapEngines (int n)
10313 {   // swap settings for first engine and other engine (so far only some selected options)
10314     int h;
10315     char *p;
10316     if(n == 0) return;
10317     SWAP(directory, p)
10318     SWAP(chessProgram, p)
10319     SWAP(isUCI, h)
10320     SWAP(hasOwnBookUCI, h)
10321     SWAP(protocolVersion, h)
10322     SWAP(reuse, h)
10323     SWAP(scoreIsAbsolute, h)
10324     SWAP(timeOdds, h)
10325     SWAP(logo, p)
10326     SWAP(pgnName, p)
10327     SWAP(pvSAN, h)
10328     SWAP(engOptions, p)
10329     SWAP(engInitString, p)
10330     SWAP(computerString, p)
10331     SWAP(features, p)
10332     SWAP(fenOverride, p)
10333     SWAP(NPS, h)
10334     SWAP(accumulateTC, h)
10335     SWAP(host, p)
10336 }
10337
10338 int
10339 GetEngineLine (char *s, int n)
10340 {
10341     int i;
10342     char buf[MSG_SIZ];
10343     extern char *icsNames;
10344     if(!s || !*s) return 0;
10345     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10346     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10347     if(!mnemonic[i]) return 0;
10348     if(n == 11) return 1; // just testing if there was a match
10349     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10350     if(n == 1) SwapEngines(n);
10351     ParseArgsFromString(buf);
10352     if(n == 1) SwapEngines(n);
10353     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10354         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10355         ParseArgsFromString(buf);
10356     }
10357     return 1;
10358 }
10359
10360 int
10361 SetPlayer (int player, char *p)
10362 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10363     int i;
10364     char buf[MSG_SIZ], *engineName;
10365     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10366     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10367     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10368     if(mnemonic[i]) {
10369         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10370         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10371         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10372         ParseArgsFromString(buf);
10373     } else { // no engine with this nickname is installed!
10374         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10375         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10376         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10377         ModeHighlight();
10378         DisplayError(buf, 0);
10379         return 0;
10380     }
10381     free(engineName);
10382     return i;
10383 }
10384
10385 char *recentEngines;
10386
10387 void
10388 RecentEngineEvent (int nr)
10389 {
10390     int n;
10391 //    SwapEngines(1); // bump first to second
10392 //    ReplaceEngine(&second, 1); // and load it there
10393     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10394     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10395     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10396         ReplaceEngine(&first, 0);
10397         FloatToFront(&appData.recentEngineList, command[n]);
10398     }
10399 }
10400
10401 int
10402 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10403 {   // determine players from game number
10404     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10405
10406     if(appData.tourneyType == 0) {
10407         roundsPerCycle = (nPlayers - 1) | 1;
10408         pairingsPerRound = nPlayers / 2;
10409     } else if(appData.tourneyType > 0) {
10410         roundsPerCycle = nPlayers - appData.tourneyType;
10411         pairingsPerRound = appData.tourneyType;
10412     }
10413     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10414     gamesPerCycle = gamesPerRound * roundsPerCycle;
10415     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10416     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10417     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10418     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10419     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10420     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10421
10422     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10423     if(appData.roundSync) *syncInterval = gamesPerRound;
10424
10425     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10426
10427     if(appData.tourneyType == 0) {
10428         if(curPairing == (nPlayers-1)/2 ) {
10429             *whitePlayer = curRound;
10430             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10431         } else {
10432             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10433             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10434             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10435             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10436         }
10437     } else if(appData.tourneyType > 1) {
10438         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10439         *whitePlayer = curRound + appData.tourneyType;
10440     } else if(appData.tourneyType > 0) {
10441         *whitePlayer = curPairing;
10442         *blackPlayer = curRound + appData.tourneyType;
10443     }
10444
10445     // take care of white/black alternation per round.
10446     // For cycles and games this is already taken care of by default, derived from matchGame!
10447     return curRound & 1;
10448 }
10449
10450 int
10451 NextTourneyGame (int nr, int *swapColors)
10452 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10453     char *p, *q;
10454     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10455     FILE *tf;
10456     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10457     tf = fopen(appData.tourneyFile, "r");
10458     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10459     ParseArgsFromFile(tf); fclose(tf);
10460     InitTimeControls(); // TC might be altered from tourney file
10461
10462     nPlayers = CountPlayers(appData.participants); // count participants
10463     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10464     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10465
10466     if(syncInterval) {
10467         p = q = appData.results;
10468         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10469         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10470             DisplayMessage(_("Waiting for other game(s)"),"");
10471             waitingForGame = TRUE;
10472             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10473             return 0;
10474         }
10475         waitingForGame = FALSE;
10476     }
10477
10478     if(appData.tourneyType < 0) {
10479         if(nr>=0 && !pairingReceived) {
10480             char buf[1<<16];
10481             if(pairing.pr == NoProc) {
10482                 if(!appData.pairingEngine[0]) {
10483                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10484                     return 0;
10485                 }
10486                 StartChessProgram(&pairing); // starts the pairing engine
10487             }
10488             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10489             SendToProgram(buf, &pairing);
10490             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10491             SendToProgram(buf, &pairing);
10492             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10493         }
10494         pairingReceived = 0;                              // ... so we continue here
10495         *swapColors = 0;
10496         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10497         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10498         matchGame = 1; roundNr = nr / syncInterval + 1;
10499     }
10500
10501     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10502
10503     // redefine engines, engine dir, etc.
10504     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10505     if(first.pr == NoProc) {
10506       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10507       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10508     }
10509     if(second.pr == NoProc) {
10510       SwapEngines(1);
10511       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10512       SwapEngines(1);         // and make that valid for second engine by swapping
10513       InitEngine(&second, 1);
10514     }
10515     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10516     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10517     return OK;
10518 }
10519
10520 void
10521 NextMatchGame ()
10522 {   // performs game initialization that does not invoke engines, and then tries to start the game
10523     int res, firstWhite, swapColors = 0;
10524     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10525     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
10526         char buf[MSG_SIZ];
10527         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10528         if(strcmp(buf, currentDebugFile)) { // name has changed
10529             FILE *f = fopen(buf, "w");
10530             if(f) { // if opening the new file failed, just keep using the old one
10531                 ASSIGN(currentDebugFile, buf);
10532                 fclose(debugFP);
10533                 debugFP = f;
10534             }
10535             if(appData.serverFileName) {
10536                 if(serverFP) fclose(serverFP);
10537                 serverFP = fopen(appData.serverFileName, "w");
10538                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10539                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10540             }
10541         }
10542     }
10543     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10544     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10545     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10546     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10547     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10548     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10549     Reset(FALSE, first.pr != NoProc);
10550     res = LoadGameOrPosition(matchGame); // setup game
10551     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10552     if(!res) return; // abort when bad game/pos file
10553     TwoMachinesEvent();
10554 }
10555
10556 void
10557 UserAdjudicationEvent (int result)
10558 {
10559     ChessMove gameResult = GameIsDrawn;
10560
10561     if( result > 0 ) {
10562         gameResult = WhiteWins;
10563     }
10564     else if( result < 0 ) {
10565         gameResult = BlackWins;
10566     }
10567
10568     if( gameMode == TwoMachinesPlay ) {
10569         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10570     }
10571 }
10572
10573
10574 // [HGM] save: calculate checksum of game to make games easily identifiable
10575 int
10576 StringCheckSum (char *s)
10577 {
10578         int i = 0;
10579         if(s==NULL) return 0;
10580         while(*s) i = i*259 + *s++;
10581         return i;
10582 }
10583
10584 int
10585 GameCheckSum ()
10586 {
10587         int i, sum=0;
10588         for(i=backwardMostMove; i<forwardMostMove; i++) {
10589                 sum += pvInfoList[i].depth;
10590                 sum += StringCheckSum(parseList[i]);
10591                 sum += StringCheckSum(commentList[i]);
10592                 sum *= 261;
10593         }
10594         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10595         return sum + StringCheckSum(commentList[i]);
10596 } // end of save patch
10597
10598 void
10599 GameEnds (ChessMove result, char *resultDetails, int whosays)
10600 {
10601     GameMode nextGameMode;
10602     int isIcsGame;
10603     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10604
10605     if(endingGame) return; /* [HGM] crash: forbid recursion */
10606     endingGame = 1;
10607     if(twoBoards) { // [HGM] dual: switch back to one board
10608         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10609         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10610     }
10611     if (appData.debugMode) {
10612       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10613               result, resultDetails ? resultDetails : "(null)", whosays);
10614     }
10615
10616     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10617
10618     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10619
10620     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10621         /* If we are playing on ICS, the server decides when the
10622            game is over, but the engine can offer to draw, claim
10623            a draw, or resign.
10624          */
10625 #if ZIPPY
10626         if (appData.zippyPlay && first.initDone) {
10627             if (result == GameIsDrawn) {
10628                 /* In case draw still needs to be claimed */
10629                 SendToICS(ics_prefix);
10630                 SendToICS("draw\n");
10631             } else if (StrCaseStr(resultDetails, "resign")) {
10632                 SendToICS(ics_prefix);
10633                 SendToICS("resign\n");
10634             }
10635         }
10636 #endif
10637         endingGame = 0; /* [HGM] crash */
10638         return;
10639     }
10640
10641     /* If we're loading the game from a file, stop */
10642     if (whosays == GE_FILE) {
10643       (void) StopLoadGameTimer();
10644       gameFileFP = NULL;
10645     }
10646
10647     /* Cancel draw offers */
10648     first.offeredDraw = second.offeredDraw = 0;
10649
10650     /* If this is an ICS game, only ICS can really say it's done;
10651        if not, anyone can. */
10652     isIcsGame = (gameMode == IcsPlayingWhite ||
10653                  gameMode == IcsPlayingBlack ||
10654                  gameMode == IcsObserving    ||
10655                  gameMode == IcsExamining);
10656
10657     if (!isIcsGame || whosays == GE_ICS) {
10658         /* OK -- not an ICS game, or ICS said it was done */
10659         StopClocks();
10660         if (!isIcsGame && !appData.noChessProgram)
10661           SetUserThinkingEnables();
10662
10663         /* [HGM] if a machine claims the game end we verify this claim */
10664         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10665             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10666                 char claimer;
10667                 ChessMove trueResult = (ChessMove) -1;
10668
10669                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10670                                             first.twoMachinesColor[0] :
10671                                             second.twoMachinesColor[0] ;
10672
10673                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10674                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10675                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10676                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10677                 } else
10678                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10679                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10680                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10681                 } else
10682                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10683                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10684                 }
10685
10686                 // now verify win claims, but not in drop games, as we don't understand those yet
10687                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10688                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10689                     (result == WhiteWins && claimer == 'w' ||
10690                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10691                       if (appData.debugMode) {
10692                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10693                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10694                       }
10695                       if(result != trueResult) {
10696                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10697                               result = claimer == 'w' ? BlackWins : WhiteWins;
10698                               resultDetails = buf;
10699                       }
10700                 } else
10701                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10702                     && (forwardMostMove <= backwardMostMove ||
10703                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10704                         (claimer=='b')==(forwardMostMove&1))
10705                                                                                   ) {
10706                       /* [HGM] verify: draws that were not flagged are false claims */
10707                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10708                       result = claimer == 'w' ? BlackWins : WhiteWins;
10709                       resultDetails = buf;
10710                 }
10711                 /* (Claiming a loss is accepted no questions asked!) */
10712             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
10713                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
10714                 result = GameUnfinished;
10715                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
10716             }
10717             /* [HGM] bare: don't allow bare King to win */
10718             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10719                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10720                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10721                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10722                && result != GameIsDrawn)
10723             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10724                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10725                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10726                         if(p >= 0 && p <= (int)WhiteKing) k++;
10727                 }
10728                 if (appData.debugMode) {
10729                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10730                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10731                 }
10732                 if(k <= 1) {
10733                         result = GameIsDrawn;
10734                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10735                         resultDetails = buf;
10736                 }
10737             }
10738         }
10739
10740
10741         if(serverMoves != NULL && !loadFlag) { char c = '=';
10742             if(result==WhiteWins) c = '+';
10743             if(result==BlackWins) c = '-';
10744             if(resultDetails != NULL)
10745                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10746         }
10747         if (resultDetails != NULL) {
10748             gameInfo.result = result;
10749             gameInfo.resultDetails = StrSave(resultDetails);
10750
10751             /* display last move only if game was not loaded from file */
10752             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10753                 DisplayMove(currentMove - 1);
10754
10755             if (forwardMostMove != 0) {
10756                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10757                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10758                                                                 ) {
10759                     if (*appData.saveGameFile != NULLCHAR) {
10760                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
10761                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
10762                         else
10763                         SaveGameToFile(appData.saveGameFile, TRUE);
10764                     } else if (appData.autoSaveGames) {
10765                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
10766                     }
10767                     if (*appData.savePositionFile != NULLCHAR) {
10768                         SavePositionToFile(appData.savePositionFile);
10769                     }
10770                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
10771                 }
10772             }
10773
10774             /* Tell program how game ended in case it is learning */
10775             /* [HGM] Moved this to after saving the PGN, just in case */
10776             /* engine died and we got here through time loss. In that */
10777             /* case we will get a fatal error writing the pipe, which */
10778             /* would otherwise lose us the PGN.                       */
10779             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10780             /* output during GameEnds should never be fatal anymore   */
10781             if (gameMode == MachinePlaysWhite ||
10782                 gameMode == MachinePlaysBlack ||
10783                 gameMode == TwoMachinesPlay ||
10784                 gameMode == IcsPlayingWhite ||
10785                 gameMode == IcsPlayingBlack ||
10786                 gameMode == BeginningOfGame) {
10787                 char buf[MSG_SIZ];
10788                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10789                         resultDetails);
10790                 if (first.pr != NoProc) {
10791                     SendToProgram(buf, &first);
10792                 }
10793                 if (second.pr != NoProc &&
10794                     gameMode == TwoMachinesPlay) {
10795                     SendToProgram(buf, &second);
10796                 }
10797             }
10798         }
10799
10800         if (appData.icsActive) {
10801             if (appData.quietPlay &&
10802                 (gameMode == IcsPlayingWhite ||
10803                  gameMode == IcsPlayingBlack)) {
10804                 SendToICS(ics_prefix);
10805                 SendToICS("set shout 1\n");
10806             }
10807             nextGameMode = IcsIdle;
10808             ics_user_moved = FALSE;
10809             /* clean up premove.  It's ugly when the game has ended and the
10810              * premove highlights are still on the board.
10811              */
10812             if (gotPremove) {
10813               gotPremove = FALSE;
10814               ClearPremoveHighlights();
10815               DrawPosition(FALSE, boards[currentMove]);
10816             }
10817             if (whosays == GE_ICS) {
10818                 switch (result) {
10819                 case WhiteWins:
10820                     if (gameMode == IcsPlayingWhite)
10821                         PlayIcsWinSound();
10822                     else if(gameMode == IcsPlayingBlack)
10823                         PlayIcsLossSound();
10824                     break;
10825                 case BlackWins:
10826                     if (gameMode == IcsPlayingBlack)
10827                         PlayIcsWinSound();
10828                     else if(gameMode == IcsPlayingWhite)
10829                         PlayIcsLossSound();
10830                     break;
10831                 case GameIsDrawn:
10832                     PlayIcsDrawSound();
10833                     break;
10834                 default:
10835                     PlayIcsUnfinishedSound();
10836                 }
10837             }
10838         } else if (gameMode == EditGame ||
10839                    gameMode == PlayFromGameFile ||
10840                    gameMode == AnalyzeMode ||
10841                    gameMode == AnalyzeFile) {
10842             nextGameMode = gameMode;
10843         } else {
10844             nextGameMode = EndOfGame;
10845         }
10846         pausing = FALSE;
10847         ModeHighlight();
10848     } else {
10849         nextGameMode = gameMode;
10850     }
10851
10852     if (appData.noChessProgram) {
10853         gameMode = nextGameMode;
10854         ModeHighlight();
10855         endingGame = 0; /* [HGM] crash */
10856         return;
10857     }
10858
10859     if (first.reuse) {
10860         /* Put first chess program into idle state */
10861         if (first.pr != NoProc &&
10862             (gameMode == MachinePlaysWhite ||
10863              gameMode == MachinePlaysBlack ||
10864              gameMode == TwoMachinesPlay ||
10865              gameMode == IcsPlayingWhite ||
10866              gameMode == IcsPlayingBlack ||
10867              gameMode == BeginningOfGame)) {
10868             SendToProgram("force\n", &first);
10869             if (first.usePing) {
10870               char buf[MSG_SIZ];
10871               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10872               SendToProgram(buf, &first);
10873             }
10874         }
10875     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10876         /* Kill off first chess program */
10877         if (first.isr != NULL)
10878           RemoveInputSource(first.isr);
10879         first.isr = NULL;
10880
10881         if (first.pr != NoProc) {
10882             ExitAnalyzeMode();
10883             DoSleep( appData.delayBeforeQuit );
10884             SendToProgram("quit\n", &first);
10885             DoSleep( appData.delayAfterQuit );
10886             DestroyChildProcess(first.pr, first.useSigterm);
10887             first.reload = TRUE;
10888         }
10889         first.pr = NoProc;
10890     }
10891     if (second.reuse) {
10892         /* Put second chess program into idle state */
10893         if (second.pr != NoProc &&
10894             gameMode == TwoMachinesPlay) {
10895             SendToProgram("force\n", &second);
10896             if (second.usePing) {
10897               char buf[MSG_SIZ];
10898               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10899               SendToProgram(buf, &second);
10900             }
10901         }
10902     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10903         /* Kill off second chess program */
10904         if (second.isr != NULL)
10905           RemoveInputSource(second.isr);
10906         second.isr = NULL;
10907
10908         if (second.pr != NoProc) {
10909             DoSleep( appData.delayBeforeQuit );
10910             SendToProgram("quit\n", &second);
10911             DoSleep( appData.delayAfterQuit );
10912             DestroyChildProcess(second.pr, second.useSigterm);
10913             second.reload = TRUE;
10914         }
10915         second.pr = NoProc;
10916     }
10917
10918     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
10919         char resChar = '=';
10920         switch (result) {
10921         case WhiteWins:
10922           resChar = '+';
10923           if (first.twoMachinesColor[0] == 'w') {
10924             first.matchWins++;
10925           } else {
10926             second.matchWins++;
10927           }
10928           break;
10929         case BlackWins:
10930           resChar = '-';
10931           if (first.twoMachinesColor[0] == 'b') {
10932             first.matchWins++;
10933           } else {
10934             second.matchWins++;
10935           }
10936           break;
10937         case GameUnfinished:
10938           resChar = ' ';
10939         default:
10940           break;
10941         }
10942
10943         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10944         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10945             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10946             ReserveGame(nextGame, resChar); // sets nextGame
10947             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10948             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10949         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10950
10951         if (nextGame <= appData.matchGames && !abortMatch) {
10952             gameMode = nextGameMode;
10953             matchGame = nextGame; // this will be overruled in tourney mode!
10954             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10955             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10956             endingGame = 0; /* [HGM] crash */
10957             return;
10958         } else {
10959             gameMode = nextGameMode;
10960             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10961                      first.tidy, second.tidy,
10962                      first.matchWins, second.matchWins,
10963                      appData.matchGames - (first.matchWins + second.matchWins));
10964             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10965             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10966             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10967             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10968                 first.twoMachinesColor = "black\n";
10969                 second.twoMachinesColor = "white\n";
10970             } else {
10971                 first.twoMachinesColor = "white\n";
10972                 second.twoMachinesColor = "black\n";
10973             }
10974         }
10975     }
10976     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10977         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10978       ExitAnalyzeMode();
10979     gameMode = nextGameMode;
10980     ModeHighlight();
10981     endingGame = 0;  /* [HGM] crash */
10982     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10983         if(matchMode == TRUE) { // match through command line: exit with or without popup
10984             if(ranking) {
10985                 ToNrEvent(forwardMostMove);
10986                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10987                 else ExitEvent(0);
10988             } else DisplayFatalError(buf, 0, 0);
10989         } else { // match through menu; just stop, with or without popup
10990             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10991             ModeHighlight();
10992             if(ranking){
10993                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10994             } else DisplayNote(buf);
10995       }
10996       if(ranking) free(ranking);
10997     }
10998 }
10999
11000 /* Assumes program was just initialized (initString sent).
11001    Leaves program in force mode. */
11002 void
11003 FeedMovesToProgram (ChessProgramState *cps, int upto)
11004 {
11005     int i;
11006
11007     if (appData.debugMode)
11008       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11009               startedFromSetupPosition ? "position and " : "",
11010               backwardMostMove, upto, cps->which);
11011     if(currentlyInitializedVariant != gameInfo.variant) {
11012       char buf[MSG_SIZ];
11013         // [HGM] variantswitch: make engine aware of new variant
11014         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11015                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11016         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11017         SendToProgram(buf, cps);
11018         currentlyInitializedVariant = gameInfo.variant;
11019     }
11020     SendToProgram("force\n", cps);
11021     if (startedFromSetupPosition) {
11022         SendBoard(cps, backwardMostMove);
11023     if (appData.debugMode) {
11024         fprintf(debugFP, "feedMoves\n");
11025     }
11026     }
11027     for (i = backwardMostMove; i < upto; i++) {
11028         SendMoveToProgram(i, cps);
11029     }
11030 }
11031
11032
11033 int
11034 ResurrectChessProgram ()
11035 {
11036      /* The chess program may have exited.
11037         If so, restart it and feed it all the moves made so far. */
11038     static int doInit = 0;
11039
11040     if (appData.noChessProgram) return 1;
11041
11042     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11043         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11044         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11045         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11046     } else {
11047         if (first.pr != NoProc) return 1;
11048         StartChessProgram(&first);
11049     }
11050     InitChessProgram(&first, FALSE);
11051     FeedMovesToProgram(&first, currentMove);
11052
11053     if (!first.sendTime) {
11054         /* can't tell gnuchess what its clock should read,
11055            so we bow to its notion. */
11056         ResetClocks();
11057         timeRemaining[0][currentMove] = whiteTimeRemaining;
11058         timeRemaining[1][currentMove] = blackTimeRemaining;
11059     }
11060
11061     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11062                 appData.icsEngineAnalyze) && first.analysisSupport) {
11063       SendToProgram("analyze\n", &first);
11064       first.analyzing = TRUE;
11065     }
11066     return 1;
11067 }
11068
11069 /*
11070  * Button procedures
11071  */
11072 void
11073 Reset (int redraw, int init)
11074 {
11075     int i;
11076
11077     if (appData.debugMode) {
11078         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11079                 redraw, init, gameMode);
11080     }
11081     CleanupTail(); // [HGM] vari: delete any stored variations
11082     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11083     pausing = pauseExamInvalid = FALSE;
11084     startedFromSetupPosition = blackPlaysFirst = FALSE;
11085     firstMove = TRUE;
11086     whiteFlag = blackFlag = FALSE;
11087     userOfferedDraw = FALSE;
11088     hintRequested = bookRequested = FALSE;
11089     first.maybeThinking = FALSE;
11090     second.maybeThinking = FALSE;
11091     first.bookSuspend = FALSE; // [HGM] book
11092     second.bookSuspend = FALSE;
11093     thinkOutput[0] = NULLCHAR;
11094     lastHint[0] = NULLCHAR;
11095     ClearGameInfo(&gameInfo);
11096     gameInfo.variant = StringToVariant(appData.variant);
11097     ics_user_moved = ics_clock_paused = FALSE;
11098     ics_getting_history = H_FALSE;
11099     ics_gamenum = -1;
11100     white_holding[0] = black_holding[0] = NULLCHAR;
11101     ClearProgramStats();
11102     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11103
11104     ResetFrontEnd();
11105     ClearHighlights();
11106     flipView = appData.flipView;
11107     ClearPremoveHighlights();
11108     gotPremove = FALSE;
11109     alarmSounded = FALSE;
11110
11111     GameEnds(EndOfFile, NULL, GE_PLAYER);
11112     if(appData.serverMovesName != NULL) {
11113         /* [HGM] prepare to make moves file for broadcasting */
11114         clock_t t = clock();
11115         if(serverMoves != NULL) fclose(serverMoves);
11116         serverMoves = fopen(appData.serverMovesName, "r");
11117         if(serverMoves != NULL) {
11118             fclose(serverMoves);
11119             /* delay 15 sec before overwriting, so all clients can see end */
11120             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11121         }
11122         serverMoves = fopen(appData.serverMovesName, "w");
11123     }
11124
11125     ExitAnalyzeMode();
11126     gameMode = BeginningOfGame;
11127     ModeHighlight();
11128     if(appData.icsActive) gameInfo.variant = VariantNormal;
11129     currentMove = forwardMostMove = backwardMostMove = 0;
11130     MarkTargetSquares(1);
11131     InitPosition(redraw);
11132     for (i = 0; i < MAX_MOVES; i++) {
11133         if (commentList[i] != NULL) {
11134             free(commentList[i]);
11135             commentList[i] = NULL;
11136         }
11137     }
11138     ResetClocks();
11139     timeRemaining[0][0] = whiteTimeRemaining;
11140     timeRemaining[1][0] = blackTimeRemaining;
11141
11142     if (first.pr == NoProc) {
11143         StartChessProgram(&first);
11144     }
11145     if (init) {
11146             InitChessProgram(&first, startedFromSetupPosition);
11147     }
11148     DisplayTitle("");
11149     DisplayMessage("", "");
11150     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11151     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11152     ClearMap();        // [HGM] exclude: invalidate map
11153 }
11154
11155 void
11156 AutoPlayGameLoop ()
11157 {
11158     for (;;) {
11159         if (!AutoPlayOneMove())
11160           return;
11161         if (matchMode || appData.timeDelay == 0)
11162           continue;
11163         if (appData.timeDelay < 0)
11164           return;
11165         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11166         break;
11167     }
11168 }
11169
11170 void
11171 AnalyzeNextGame()
11172 {
11173     ReloadGame(1); // next game
11174 }
11175
11176 int
11177 AutoPlayOneMove ()
11178 {
11179     int fromX, fromY, toX, toY;
11180
11181     if (appData.debugMode) {
11182       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11183     }
11184
11185     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11186       return FALSE;
11187
11188     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11189       pvInfoList[currentMove].depth = programStats.depth;
11190       pvInfoList[currentMove].score = programStats.score;
11191       pvInfoList[currentMove].time  = 0;
11192       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11193       else { // append analysis of final position as comment
11194         char buf[MSG_SIZ];
11195         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11196         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11197       }
11198       programStats.depth = 0;
11199     }
11200
11201     if (currentMove >= forwardMostMove) {
11202       if(gameMode == AnalyzeFile) {
11203           if(appData.loadGameIndex == -1) {
11204             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11205           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11206           } else {
11207           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11208         }
11209       }
11210 //      gameMode = EndOfGame;
11211 //      ModeHighlight();
11212
11213       /* [AS] Clear current move marker at the end of a game */
11214       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11215
11216       return FALSE;
11217     }
11218
11219     toX = moveList[currentMove][2] - AAA;
11220     toY = moveList[currentMove][3] - ONE;
11221
11222     if (moveList[currentMove][1] == '@') {
11223         if (appData.highlightLastMove) {
11224             SetHighlights(-1, -1, toX, toY);
11225         }
11226     } else {
11227         fromX = moveList[currentMove][0] - AAA;
11228         fromY = moveList[currentMove][1] - ONE;
11229
11230         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11231
11232         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11233
11234         if (appData.highlightLastMove) {
11235             SetHighlights(fromX, fromY, toX, toY);
11236         }
11237     }
11238     DisplayMove(currentMove);
11239     SendMoveToProgram(currentMove++, &first);
11240     DisplayBothClocks();
11241     DrawPosition(FALSE, boards[currentMove]);
11242     // [HGM] PV info: always display, routine tests if empty
11243     DisplayComment(currentMove - 1, commentList[currentMove]);
11244     return TRUE;
11245 }
11246
11247
11248 int
11249 LoadGameOneMove (ChessMove readAhead)
11250 {
11251     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11252     char promoChar = NULLCHAR;
11253     ChessMove moveType;
11254     char move[MSG_SIZ];
11255     char *p, *q;
11256
11257     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11258         gameMode != AnalyzeMode && gameMode != Training) {
11259         gameFileFP = NULL;
11260         return FALSE;
11261     }
11262
11263     yyboardindex = forwardMostMove;
11264     if (readAhead != EndOfFile) {
11265       moveType = readAhead;
11266     } else {
11267       if (gameFileFP == NULL)
11268           return FALSE;
11269       moveType = (ChessMove) Myylex();
11270     }
11271
11272     done = FALSE;
11273     switch (moveType) {
11274       case Comment:
11275         if (appData.debugMode)
11276           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11277         p = yy_text;
11278
11279         /* append the comment but don't display it */
11280         AppendComment(currentMove, p, FALSE);
11281         return TRUE;
11282
11283       case WhiteCapturesEnPassant:
11284       case BlackCapturesEnPassant:
11285       case WhitePromotion:
11286       case BlackPromotion:
11287       case WhiteNonPromotion:
11288       case BlackNonPromotion:
11289       case NormalMove:
11290       case WhiteKingSideCastle:
11291       case WhiteQueenSideCastle:
11292       case BlackKingSideCastle:
11293       case BlackQueenSideCastle:
11294       case WhiteKingSideCastleWild:
11295       case WhiteQueenSideCastleWild:
11296       case BlackKingSideCastleWild:
11297       case BlackQueenSideCastleWild:
11298       /* PUSH Fabien */
11299       case WhiteHSideCastleFR:
11300       case WhiteASideCastleFR:
11301       case BlackHSideCastleFR:
11302       case BlackASideCastleFR:
11303       /* POP Fabien */
11304         if (appData.debugMode)
11305           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11306         fromX = currentMoveString[0] - AAA;
11307         fromY = currentMoveString[1] - ONE;
11308         toX = currentMoveString[2] - AAA;
11309         toY = currentMoveString[3] - ONE;
11310         promoChar = currentMoveString[4];
11311         break;
11312
11313       case WhiteDrop:
11314       case BlackDrop:
11315         if (appData.debugMode)
11316           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11317         fromX = moveType == WhiteDrop ?
11318           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11319         (int) CharToPiece(ToLower(currentMoveString[0]));
11320         fromY = DROP_RANK;
11321         toX = currentMoveString[2] - AAA;
11322         toY = currentMoveString[3] - ONE;
11323         break;
11324
11325       case WhiteWins:
11326       case BlackWins:
11327       case GameIsDrawn:
11328       case GameUnfinished:
11329         if (appData.debugMode)
11330           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11331         p = strchr(yy_text, '{');
11332         if (p == NULL) p = strchr(yy_text, '(');
11333         if (p == NULL) {
11334             p = yy_text;
11335             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11336         } else {
11337             q = strchr(p, *p == '{' ? '}' : ')');
11338             if (q != NULL) *q = NULLCHAR;
11339             p++;
11340         }
11341         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11342         GameEnds(moveType, p, GE_FILE);
11343         done = TRUE;
11344         if (cmailMsgLoaded) {
11345             ClearHighlights();
11346             flipView = WhiteOnMove(currentMove);
11347             if (moveType == GameUnfinished) flipView = !flipView;
11348             if (appData.debugMode)
11349               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11350         }
11351         break;
11352
11353       case EndOfFile:
11354         if (appData.debugMode)
11355           fprintf(debugFP, "Parser hit end of file\n");
11356         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11357           case MT_NONE:
11358           case MT_CHECK:
11359             break;
11360           case MT_CHECKMATE:
11361           case MT_STAINMATE:
11362             if (WhiteOnMove(currentMove)) {
11363                 GameEnds(BlackWins, "Black mates", GE_FILE);
11364             } else {
11365                 GameEnds(WhiteWins, "White mates", GE_FILE);
11366             }
11367             break;
11368           case MT_STALEMATE:
11369             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11370             break;
11371         }
11372         done = TRUE;
11373         break;
11374
11375       case MoveNumberOne:
11376         if (lastLoadGameStart == GNUChessGame) {
11377             /* GNUChessGames have numbers, but they aren't move numbers */
11378             if (appData.debugMode)
11379               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11380                       yy_text, (int) moveType);
11381             return LoadGameOneMove(EndOfFile); /* tail recursion */
11382         }
11383         /* else fall thru */
11384
11385       case XBoardGame:
11386       case GNUChessGame:
11387       case PGNTag:
11388         /* Reached start of next game in file */
11389         if (appData.debugMode)
11390           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11391         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11392           case MT_NONE:
11393           case MT_CHECK:
11394             break;
11395           case MT_CHECKMATE:
11396           case MT_STAINMATE:
11397             if (WhiteOnMove(currentMove)) {
11398                 GameEnds(BlackWins, "Black mates", GE_FILE);
11399             } else {
11400                 GameEnds(WhiteWins, "White mates", GE_FILE);
11401             }
11402             break;
11403           case MT_STALEMATE:
11404             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11405             break;
11406         }
11407         done = TRUE;
11408         break;
11409
11410       case PositionDiagram:     /* should not happen; ignore */
11411       case ElapsedTime:         /* ignore */
11412       case NAG:                 /* ignore */
11413         if (appData.debugMode)
11414           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11415                   yy_text, (int) moveType);
11416         return LoadGameOneMove(EndOfFile); /* tail recursion */
11417
11418       case IllegalMove:
11419         if (appData.testLegality) {
11420             if (appData.debugMode)
11421               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11422             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11423                     (forwardMostMove / 2) + 1,
11424                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11425             DisplayError(move, 0);
11426             done = TRUE;
11427         } else {
11428             if (appData.debugMode)
11429               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11430                       yy_text, currentMoveString);
11431             fromX = currentMoveString[0] - AAA;
11432             fromY = currentMoveString[1] - ONE;
11433             toX = currentMoveString[2] - AAA;
11434             toY = currentMoveString[3] - ONE;
11435             promoChar = currentMoveString[4];
11436         }
11437         break;
11438
11439       case AmbiguousMove:
11440         if (appData.debugMode)
11441           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11442         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11443                 (forwardMostMove / 2) + 1,
11444                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11445         DisplayError(move, 0);
11446         done = TRUE;
11447         break;
11448
11449       default:
11450       case ImpossibleMove:
11451         if (appData.debugMode)
11452           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11453         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11454                 (forwardMostMove / 2) + 1,
11455                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11456         DisplayError(move, 0);
11457         done = TRUE;
11458         break;
11459     }
11460
11461     if (done) {
11462         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11463             DrawPosition(FALSE, boards[currentMove]);
11464             DisplayBothClocks();
11465             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11466               DisplayComment(currentMove - 1, commentList[currentMove]);
11467         }
11468         (void) StopLoadGameTimer();
11469         gameFileFP = NULL;
11470         cmailOldMove = forwardMostMove;
11471         return FALSE;
11472     } else {
11473         /* currentMoveString is set as a side-effect of yylex */
11474
11475         thinkOutput[0] = NULLCHAR;
11476         MakeMove(fromX, fromY, toX, toY, promoChar);
11477         currentMove = forwardMostMove;
11478         return TRUE;
11479     }
11480 }
11481
11482 /* Load the nth game from the given file */
11483 int
11484 LoadGameFromFile (char *filename, int n, char *title, int useList)
11485 {
11486     FILE *f;
11487     char buf[MSG_SIZ];
11488
11489     if (strcmp(filename, "-") == 0) {
11490         f = stdin;
11491         title = "stdin";
11492     } else {
11493         f = fopen(filename, "rb");
11494         if (f == NULL) {
11495           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11496             DisplayError(buf, errno);
11497             return FALSE;
11498         }
11499     }
11500     if (fseek(f, 0, 0) == -1) {
11501         /* f is not seekable; probably a pipe */
11502         useList = FALSE;
11503     }
11504     if (useList && n == 0) {
11505         int error = GameListBuild(f);
11506         if (error) {
11507             DisplayError(_("Cannot build game list"), error);
11508         } else if (!ListEmpty(&gameList) &&
11509                    ((ListGame *) gameList.tailPred)->number > 1) {
11510             GameListPopUp(f, title);
11511             return TRUE;
11512         }
11513         GameListDestroy();
11514         n = 1;
11515     }
11516     if (n == 0) n = 1;
11517     return LoadGame(f, n, title, FALSE);
11518 }
11519
11520
11521 void
11522 MakeRegisteredMove ()
11523 {
11524     int fromX, fromY, toX, toY;
11525     char promoChar;
11526     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11527         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11528           case CMAIL_MOVE:
11529           case CMAIL_DRAW:
11530             if (appData.debugMode)
11531               fprintf(debugFP, "Restoring %s for game %d\n",
11532                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11533
11534             thinkOutput[0] = NULLCHAR;
11535             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11536             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11537             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11538             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11539             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11540             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11541             MakeMove(fromX, fromY, toX, toY, promoChar);
11542             ShowMove(fromX, fromY, toX, toY);
11543
11544             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11545               case MT_NONE:
11546               case MT_CHECK:
11547                 break;
11548
11549               case MT_CHECKMATE:
11550               case MT_STAINMATE:
11551                 if (WhiteOnMove(currentMove)) {
11552                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11553                 } else {
11554                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11555                 }
11556                 break;
11557
11558               case MT_STALEMATE:
11559                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11560                 break;
11561             }
11562
11563             break;
11564
11565           case CMAIL_RESIGN:
11566             if (WhiteOnMove(currentMove)) {
11567                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11568             } else {
11569                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11570             }
11571             break;
11572
11573           case CMAIL_ACCEPT:
11574             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11575             break;
11576
11577           default:
11578             break;
11579         }
11580     }
11581
11582     return;
11583 }
11584
11585 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11586 int
11587 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11588 {
11589     int retVal;
11590
11591     if (gameNumber > nCmailGames) {
11592         DisplayError(_("No more games in this message"), 0);
11593         return FALSE;
11594     }
11595     if (f == lastLoadGameFP) {
11596         int offset = gameNumber - lastLoadGameNumber;
11597         if (offset == 0) {
11598             cmailMsg[0] = NULLCHAR;
11599             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11600                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11601                 nCmailMovesRegistered--;
11602             }
11603             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11604             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11605                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11606             }
11607         } else {
11608             if (! RegisterMove()) return FALSE;
11609         }
11610     }
11611
11612     retVal = LoadGame(f, gameNumber, title, useList);
11613
11614     /* Make move registered during previous look at this game, if any */
11615     MakeRegisteredMove();
11616
11617     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11618         commentList[currentMove]
11619           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11620         DisplayComment(currentMove - 1, commentList[currentMove]);
11621     }
11622
11623     return retVal;
11624 }
11625
11626 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11627 int
11628 ReloadGame (int offset)
11629 {
11630     int gameNumber = lastLoadGameNumber + offset;
11631     if (lastLoadGameFP == NULL) {
11632         DisplayError(_("No game has been loaded yet"), 0);
11633         return FALSE;
11634     }
11635     if (gameNumber <= 0) {
11636         DisplayError(_("Can't back up any further"), 0);
11637         return FALSE;
11638     }
11639     if (cmailMsgLoaded) {
11640         return CmailLoadGame(lastLoadGameFP, gameNumber,
11641                              lastLoadGameTitle, lastLoadGameUseList);
11642     } else {
11643         return LoadGame(lastLoadGameFP, gameNumber,
11644                         lastLoadGameTitle, lastLoadGameUseList);
11645     }
11646 }
11647
11648 int keys[EmptySquare+1];
11649
11650 int
11651 PositionMatches (Board b1, Board b2)
11652 {
11653     int r, f, sum=0;
11654     switch(appData.searchMode) {
11655         case 1: return CompareWithRights(b1, b2);
11656         case 2:
11657             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11658                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11659             }
11660             return TRUE;
11661         case 3:
11662             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11663               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11664                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11665             }
11666             return sum==0;
11667         case 4:
11668             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11669                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11670             }
11671             return sum==0;
11672     }
11673     return TRUE;
11674 }
11675
11676 #define Q_PROMO  4
11677 #define Q_EP     3
11678 #define Q_BCASTL 2
11679 #define Q_WCASTL 1
11680
11681 int pieceList[256], quickBoard[256];
11682 ChessSquare pieceType[256] = { EmptySquare };
11683 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11684 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11685 int soughtTotal, turn;
11686 Boolean epOK, flipSearch;
11687
11688 typedef struct {
11689     unsigned char piece, to;
11690 } Move;
11691
11692 #define DSIZE (250000)
11693
11694 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11695 Move *moveDatabase = initialSpace;
11696 unsigned int movePtr, dataSize = DSIZE;
11697
11698 int
11699 MakePieceList (Board board, int *counts)
11700 {
11701     int r, f, n=Q_PROMO, total=0;
11702     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11703     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11704         int sq = f + (r<<4);
11705         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11706             quickBoard[sq] = ++n;
11707             pieceList[n] = sq;
11708             pieceType[n] = board[r][f];
11709             counts[board[r][f]]++;
11710             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11711             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11712             total++;
11713         }
11714     }
11715     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11716     return total;
11717 }
11718
11719 void
11720 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11721 {
11722     int sq = fromX + (fromY<<4);
11723     int piece = quickBoard[sq];
11724     quickBoard[sq] = 0;
11725     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11726     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11727         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11728         moveDatabase[movePtr++].piece = Q_WCASTL;
11729         quickBoard[sq] = piece;
11730         piece = quickBoard[from]; quickBoard[from] = 0;
11731         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11732     } else
11733     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11734         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11735         moveDatabase[movePtr++].piece = Q_BCASTL;
11736         quickBoard[sq] = piece;
11737         piece = quickBoard[from]; quickBoard[from] = 0;
11738         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11739     } else
11740     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11741         quickBoard[(fromY<<4)+toX] = 0;
11742         moveDatabase[movePtr].piece = Q_EP;
11743         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11744         moveDatabase[movePtr].to = sq;
11745     } else
11746     if(promoPiece != pieceType[piece]) {
11747         moveDatabase[movePtr++].piece = Q_PROMO;
11748         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11749     }
11750     moveDatabase[movePtr].piece = piece;
11751     quickBoard[sq] = piece;
11752     movePtr++;
11753 }
11754
11755 int
11756 PackGame (Board board)
11757 {
11758     Move *newSpace = NULL;
11759     moveDatabase[movePtr].piece = 0; // terminate previous game
11760     if(movePtr > dataSize) {
11761         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11762         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11763         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11764         if(newSpace) {
11765             int i;
11766             Move *p = moveDatabase, *q = newSpace;
11767             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11768             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11769             moveDatabase = newSpace;
11770         } else { // calloc failed, we must be out of memory. Too bad...
11771             dataSize = 0; // prevent calloc events for all subsequent games
11772             return 0;     // and signal this one isn't cached
11773         }
11774     }
11775     movePtr++;
11776     MakePieceList(board, counts);
11777     return movePtr;
11778 }
11779
11780 int
11781 QuickCompare (Board board, int *minCounts, int *maxCounts)
11782 {   // compare according to search mode
11783     int r, f;
11784     switch(appData.searchMode)
11785     {
11786       case 1: // exact position match
11787         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11788         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11789             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11790         }
11791         break;
11792       case 2: // can have extra material on empty squares
11793         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11794             if(board[r][f] == EmptySquare) continue;
11795             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11796         }
11797         break;
11798       case 3: // material with exact Pawn structure
11799         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11800             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11801             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11802         } // fall through to material comparison
11803       case 4: // exact material
11804         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11805         break;
11806       case 6: // material range with given imbalance
11807         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11808         // fall through to range comparison
11809       case 5: // material range
11810         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11811     }
11812     return TRUE;
11813 }
11814
11815 int
11816 QuickScan (Board board, Move *move)
11817 {   // reconstruct game,and compare all positions in it
11818     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11819     do {
11820         int piece = move->piece;
11821         int to = move->to, from = pieceList[piece];
11822         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11823           if(!piece) return -1;
11824           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11825             piece = (++move)->piece;
11826             from = pieceList[piece];
11827             counts[pieceType[piece]]--;
11828             pieceType[piece] = (ChessSquare) move->to;
11829             counts[move->to]++;
11830           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11831             counts[pieceType[quickBoard[to]]]--;
11832             quickBoard[to] = 0; total--;
11833             move++;
11834             continue;
11835           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11836             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11837             from  = pieceList[piece]; // so this must be King
11838             quickBoard[from] = 0;
11839             pieceList[piece] = to;
11840             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11841             quickBoard[from] = 0; // rook
11842             quickBoard[to] = piece;
11843             to = move->to; piece = move->piece;
11844             goto aftercastle;
11845           }
11846         }
11847         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11848         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11849         quickBoard[from] = 0;
11850       aftercastle:
11851         quickBoard[to] = piece;
11852         pieceList[piece] = to;
11853         cnt++; turn ^= 3;
11854         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11855            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11856            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11857                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11858           ) {
11859             static int lastCounts[EmptySquare+1];
11860             int i;
11861             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11862             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11863         } else stretch = 0;
11864         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11865         move++;
11866     } while(1);
11867 }
11868
11869 void
11870 InitSearch ()
11871 {
11872     int r, f;
11873     flipSearch = FALSE;
11874     CopyBoard(soughtBoard, boards[currentMove]);
11875     soughtTotal = MakePieceList(soughtBoard, maxSought);
11876     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11877     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11878     CopyBoard(reverseBoard, boards[currentMove]);
11879     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11880         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11881         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11882         reverseBoard[r][f] = piece;
11883     }
11884     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
11885     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11886     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11887                  || (boards[currentMove][CASTLING][2] == NoRights ||
11888                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11889                  && (boards[currentMove][CASTLING][5] == NoRights ||
11890                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11891       ) {
11892         flipSearch = TRUE;
11893         CopyBoard(flipBoard, soughtBoard);
11894         CopyBoard(rotateBoard, reverseBoard);
11895         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11896             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11897             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11898         }
11899     }
11900     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11901     if(appData.searchMode >= 5) {
11902         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11903         MakePieceList(soughtBoard, minSought);
11904         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11905     }
11906     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11907         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11908 }
11909
11910 GameInfo dummyInfo;
11911 static int creatingBook;
11912
11913 int
11914 GameContainsPosition (FILE *f, ListGame *lg)
11915 {
11916     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11917     int fromX, fromY, toX, toY;
11918     char promoChar;
11919     static int initDone=FALSE;
11920
11921     // weed out games based on numerical tag comparison
11922     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11923     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11924     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11925     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11926     if(!initDone) {
11927         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11928         initDone = TRUE;
11929     }
11930     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11931     else CopyBoard(boards[scratch], initialPosition); // default start position
11932     if(lg->moves) {
11933         turn = btm + 1;
11934         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11935         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11936     }
11937     if(btm) plyNr++;
11938     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11939     fseek(f, lg->offset, 0);
11940     yynewfile(f);
11941     while(1) {
11942         yyboardindex = scratch;
11943         quickFlag = plyNr+1;
11944         next = Myylex();
11945         quickFlag = 0;
11946         switch(next) {
11947             case PGNTag:
11948                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11949             default:
11950                 continue;
11951
11952             case XBoardGame:
11953             case GNUChessGame:
11954                 if(plyNr) return -1; // after we have seen moves, this is for new game
11955               continue;
11956
11957             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11958             case ImpossibleMove:
11959             case WhiteWins: // game ends here with these four
11960             case BlackWins:
11961             case GameIsDrawn:
11962             case GameUnfinished:
11963                 return -1;
11964
11965             case IllegalMove:
11966                 if(appData.testLegality) return -1;
11967             case WhiteCapturesEnPassant:
11968             case BlackCapturesEnPassant:
11969             case WhitePromotion:
11970             case BlackPromotion:
11971             case WhiteNonPromotion:
11972             case BlackNonPromotion:
11973             case NormalMove:
11974             case WhiteKingSideCastle:
11975             case WhiteQueenSideCastle:
11976             case BlackKingSideCastle:
11977             case BlackQueenSideCastle:
11978             case WhiteKingSideCastleWild:
11979             case WhiteQueenSideCastleWild:
11980             case BlackKingSideCastleWild:
11981             case BlackQueenSideCastleWild:
11982             case WhiteHSideCastleFR:
11983             case WhiteASideCastleFR:
11984             case BlackHSideCastleFR:
11985             case BlackASideCastleFR:
11986                 fromX = currentMoveString[0] - AAA;
11987                 fromY = currentMoveString[1] - ONE;
11988                 toX = currentMoveString[2] - AAA;
11989                 toY = currentMoveString[3] - ONE;
11990                 promoChar = currentMoveString[4];
11991                 break;
11992             case WhiteDrop:
11993             case BlackDrop:
11994                 fromX = next == WhiteDrop ?
11995                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11996                   (int) CharToPiece(ToLower(currentMoveString[0]));
11997                 fromY = DROP_RANK;
11998                 toX = currentMoveString[2] - AAA;
11999                 toY = currentMoveString[3] - ONE;
12000                 promoChar = 0;
12001                 break;
12002         }
12003         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12004         plyNr++;
12005         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12006         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12007         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12008         if(appData.findMirror) {
12009             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12010             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12011         }
12012     }
12013 }
12014
12015 /* Load the nth game from open file f */
12016 int
12017 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12018 {
12019     ChessMove cm;
12020     char buf[MSG_SIZ];
12021     int gn = gameNumber;
12022     ListGame *lg = NULL;
12023     int numPGNTags = 0;
12024     int err, pos = -1;
12025     GameMode oldGameMode;
12026     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12027
12028     if (appData.debugMode)
12029         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12030
12031     if (gameMode == Training )
12032         SetTrainingModeOff();
12033
12034     oldGameMode = gameMode;
12035     if (gameMode != BeginningOfGame) {
12036       Reset(FALSE, TRUE);
12037     }
12038
12039     gameFileFP = f;
12040     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12041         fclose(lastLoadGameFP);
12042     }
12043
12044     if (useList) {
12045         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12046
12047         if (lg) {
12048             fseek(f, lg->offset, 0);
12049             GameListHighlight(gameNumber);
12050             pos = lg->position;
12051             gn = 1;
12052         }
12053         else {
12054             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12055               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12056             else
12057             DisplayError(_("Game number out of range"), 0);
12058             return FALSE;
12059         }
12060     } else {
12061         GameListDestroy();
12062         if (fseek(f, 0, 0) == -1) {
12063             if (f == lastLoadGameFP ?
12064                 gameNumber == lastLoadGameNumber + 1 :
12065                 gameNumber == 1) {
12066                 gn = 1;
12067             } else {
12068                 DisplayError(_("Can't seek on game file"), 0);
12069                 return FALSE;
12070             }
12071         }
12072     }
12073     lastLoadGameFP = f;
12074     lastLoadGameNumber = gameNumber;
12075     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12076     lastLoadGameUseList = useList;
12077
12078     yynewfile(f);
12079
12080     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12081       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12082                 lg->gameInfo.black);
12083             DisplayTitle(buf);
12084     } else if (*title != NULLCHAR) {
12085         if (gameNumber > 1) {
12086           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12087             DisplayTitle(buf);
12088         } else {
12089             DisplayTitle(title);
12090         }
12091     }
12092
12093     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12094         gameMode = PlayFromGameFile;
12095         ModeHighlight();
12096     }
12097
12098     currentMove = forwardMostMove = backwardMostMove = 0;
12099     CopyBoard(boards[0], initialPosition);
12100     StopClocks();
12101
12102     /*
12103      * Skip the first gn-1 games in the file.
12104      * Also skip over anything that precedes an identifiable
12105      * start of game marker, to avoid being confused by
12106      * garbage at the start of the file.  Currently
12107      * recognized start of game markers are the move number "1",
12108      * the pattern "gnuchess .* game", the pattern
12109      * "^[#;%] [^ ]* game file", and a PGN tag block.
12110      * A game that starts with one of the latter two patterns
12111      * will also have a move number 1, possibly
12112      * following a position diagram.
12113      * 5-4-02: Let's try being more lenient and allowing a game to
12114      * start with an unnumbered move.  Does that break anything?
12115      */
12116     cm = lastLoadGameStart = EndOfFile;
12117     while (gn > 0) {
12118         yyboardindex = forwardMostMove;
12119         cm = (ChessMove) Myylex();
12120         switch (cm) {
12121           case EndOfFile:
12122             if (cmailMsgLoaded) {
12123                 nCmailGames = CMAIL_MAX_GAMES - gn;
12124             } else {
12125                 Reset(TRUE, TRUE);
12126                 DisplayError(_("Game not found in file"), 0);
12127             }
12128             return FALSE;
12129
12130           case GNUChessGame:
12131           case XBoardGame:
12132             gn--;
12133             lastLoadGameStart = cm;
12134             break;
12135
12136           case MoveNumberOne:
12137             switch (lastLoadGameStart) {
12138               case GNUChessGame:
12139               case XBoardGame:
12140               case PGNTag:
12141                 break;
12142               case MoveNumberOne:
12143               case EndOfFile:
12144                 gn--;           /* count this game */
12145                 lastLoadGameStart = cm;
12146                 break;
12147               default:
12148                 /* impossible */
12149                 break;
12150             }
12151             break;
12152
12153           case PGNTag:
12154             switch (lastLoadGameStart) {
12155               case GNUChessGame:
12156               case PGNTag:
12157               case MoveNumberOne:
12158               case EndOfFile:
12159                 gn--;           /* count this game */
12160                 lastLoadGameStart = cm;
12161                 break;
12162               case XBoardGame:
12163                 lastLoadGameStart = cm; /* game counted already */
12164                 break;
12165               default:
12166                 /* impossible */
12167                 break;
12168             }
12169             if (gn > 0) {
12170                 do {
12171                     yyboardindex = forwardMostMove;
12172                     cm = (ChessMove) Myylex();
12173                 } while (cm == PGNTag || cm == Comment);
12174             }
12175             break;
12176
12177           case WhiteWins:
12178           case BlackWins:
12179           case GameIsDrawn:
12180             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12181                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12182                     != CMAIL_OLD_RESULT) {
12183                     nCmailResults ++ ;
12184                     cmailResult[  CMAIL_MAX_GAMES
12185                                 - gn - 1] = CMAIL_OLD_RESULT;
12186                 }
12187             }
12188             break;
12189
12190           case NormalMove:
12191             /* Only a NormalMove can be at the start of a game
12192              * without a position diagram. */
12193             if (lastLoadGameStart == EndOfFile ) {
12194               gn--;
12195               lastLoadGameStart = MoveNumberOne;
12196             }
12197             break;
12198
12199           default:
12200             break;
12201         }
12202     }
12203
12204     if (appData.debugMode)
12205       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12206
12207     if (cm == XBoardGame) {
12208         /* Skip any header junk before position diagram and/or move 1 */
12209         for (;;) {
12210             yyboardindex = forwardMostMove;
12211             cm = (ChessMove) Myylex();
12212
12213             if (cm == EndOfFile ||
12214                 cm == GNUChessGame || cm == XBoardGame) {
12215                 /* Empty game; pretend end-of-file and handle later */
12216                 cm = EndOfFile;
12217                 break;
12218             }
12219
12220             if (cm == MoveNumberOne || cm == PositionDiagram ||
12221                 cm == PGNTag || cm == Comment)
12222               break;
12223         }
12224     } else if (cm == GNUChessGame) {
12225         if (gameInfo.event != NULL) {
12226             free(gameInfo.event);
12227         }
12228         gameInfo.event = StrSave(yy_text);
12229     }
12230
12231     startedFromSetupPosition = FALSE;
12232     while (cm == PGNTag) {
12233         if (appData.debugMode)
12234           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12235         err = ParsePGNTag(yy_text, &gameInfo);
12236         if (!err) numPGNTags++;
12237
12238         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12239         if(gameInfo.variant != oldVariant) {
12240             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12241             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12242             InitPosition(TRUE);
12243             oldVariant = gameInfo.variant;
12244             if (appData.debugMode)
12245               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12246         }
12247
12248
12249         if (gameInfo.fen != NULL) {
12250           Board initial_position;
12251           startedFromSetupPosition = TRUE;
12252           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12253             Reset(TRUE, TRUE);
12254             DisplayError(_("Bad FEN position in file"), 0);
12255             return FALSE;
12256           }
12257           CopyBoard(boards[0], initial_position);
12258           if (blackPlaysFirst) {
12259             currentMove = forwardMostMove = backwardMostMove = 1;
12260             CopyBoard(boards[1], initial_position);
12261             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12262             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12263             timeRemaining[0][1] = whiteTimeRemaining;
12264             timeRemaining[1][1] = blackTimeRemaining;
12265             if (commentList[0] != NULL) {
12266               commentList[1] = commentList[0];
12267               commentList[0] = NULL;
12268             }
12269           } else {
12270             currentMove = forwardMostMove = backwardMostMove = 0;
12271           }
12272           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12273           {   int i;
12274               initialRulePlies = FENrulePlies;
12275               for( i=0; i< nrCastlingRights; i++ )
12276                   initialRights[i] = initial_position[CASTLING][i];
12277           }
12278           yyboardindex = forwardMostMove;
12279           free(gameInfo.fen);
12280           gameInfo.fen = NULL;
12281         }
12282
12283         yyboardindex = forwardMostMove;
12284         cm = (ChessMove) Myylex();
12285
12286         /* Handle comments interspersed among the tags */
12287         while (cm == Comment) {
12288             char *p;
12289             if (appData.debugMode)
12290               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12291             p = yy_text;
12292             AppendComment(currentMove, p, FALSE);
12293             yyboardindex = forwardMostMove;
12294             cm = (ChessMove) Myylex();
12295         }
12296     }
12297
12298     /* don't rely on existence of Event tag since if game was
12299      * pasted from clipboard the Event tag may not exist
12300      */
12301     if (numPGNTags > 0){
12302         char *tags;
12303         if (gameInfo.variant == VariantNormal) {
12304           VariantClass v = StringToVariant(gameInfo.event);
12305           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12306           if(v < VariantShogi) gameInfo.variant = v;
12307         }
12308         if (!matchMode) {
12309           if( appData.autoDisplayTags ) {
12310             tags = PGNTags(&gameInfo);
12311             TagsPopUp(tags, CmailMsg());
12312             free(tags);
12313           }
12314         }
12315     } else {
12316         /* Make something up, but don't display it now */
12317         SetGameInfo();
12318         TagsPopDown();
12319     }
12320
12321     if (cm == PositionDiagram) {
12322         int i, j;
12323         char *p;
12324         Board initial_position;
12325
12326         if (appData.debugMode)
12327           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12328
12329         if (!startedFromSetupPosition) {
12330             p = yy_text;
12331             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12332               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12333                 switch (*p) {
12334                   case '{':
12335                   case '[':
12336                   case '-':
12337                   case ' ':
12338                   case '\t':
12339                   case '\n':
12340                   case '\r':
12341                     break;
12342                   default:
12343                     initial_position[i][j++] = CharToPiece(*p);
12344                     break;
12345                 }
12346             while (*p == ' ' || *p == '\t' ||
12347                    *p == '\n' || *p == '\r') p++;
12348
12349             if (strncmp(p, "black", strlen("black"))==0)
12350               blackPlaysFirst = TRUE;
12351             else
12352               blackPlaysFirst = FALSE;
12353             startedFromSetupPosition = TRUE;
12354
12355             CopyBoard(boards[0], initial_position);
12356             if (blackPlaysFirst) {
12357                 currentMove = forwardMostMove = backwardMostMove = 1;
12358                 CopyBoard(boards[1], initial_position);
12359                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12360                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12361                 timeRemaining[0][1] = whiteTimeRemaining;
12362                 timeRemaining[1][1] = blackTimeRemaining;
12363                 if (commentList[0] != NULL) {
12364                     commentList[1] = commentList[0];
12365                     commentList[0] = NULL;
12366                 }
12367             } else {
12368                 currentMove = forwardMostMove = backwardMostMove = 0;
12369             }
12370         }
12371         yyboardindex = forwardMostMove;
12372         cm = (ChessMove) Myylex();
12373     }
12374
12375   if(!creatingBook) {
12376     if (first.pr == NoProc) {
12377         StartChessProgram(&first);
12378     }
12379     InitChessProgram(&first, FALSE);
12380     SendToProgram("force\n", &first);
12381     if (startedFromSetupPosition) {
12382         SendBoard(&first, forwardMostMove);
12383     if (appData.debugMode) {
12384         fprintf(debugFP, "Load Game\n");
12385     }
12386         DisplayBothClocks();
12387     }
12388   }
12389
12390     /* [HGM] server: flag to write setup moves in broadcast file as one */
12391     loadFlag = appData.suppressLoadMoves;
12392
12393     while (cm == Comment) {
12394         char *p;
12395         if (appData.debugMode)
12396           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12397         p = yy_text;
12398         AppendComment(currentMove, p, FALSE);
12399         yyboardindex = forwardMostMove;
12400         cm = (ChessMove) Myylex();
12401     }
12402
12403     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12404         cm == WhiteWins || cm == BlackWins ||
12405         cm == GameIsDrawn || cm == GameUnfinished) {
12406         DisplayMessage("", _("No moves in game"));
12407         if (cmailMsgLoaded) {
12408             if (appData.debugMode)
12409               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12410             ClearHighlights();
12411             flipView = FALSE;
12412         }
12413         DrawPosition(FALSE, boards[currentMove]);
12414         DisplayBothClocks();
12415         gameMode = EditGame;
12416         ModeHighlight();
12417         gameFileFP = NULL;
12418         cmailOldMove = 0;
12419         return TRUE;
12420     }
12421
12422     // [HGM] PV info: routine tests if comment empty
12423     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12424         DisplayComment(currentMove - 1, commentList[currentMove]);
12425     }
12426     if (!matchMode && appData.timeDelay != 0)
12427       DrawPosition(FALSE, boards[currentMove]);
12428
12429     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12430       programStats.ok_to_send = 1;
12431     }
12432
12433     /* if the first token after the PGN tags is a move
12434      * and not move number 1, retrieve it from the parser
12435      */
12436     if (cm != MoveNumberOne)
12437         LoadGameOneMove(cm);
12438
12439     /* load the remaining moves from the file */
12440     while (LoadGameOneMove(EndOfFile)) {
12441       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12442       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12443     }
12444
12445     /* rewind to the start of the game */
12446     currentMove = backwardMostMove;
12447
12448     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12449
12450     if (oldGameMode == AnalyzeFile) {
12451       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12452       AnalyzeFileEvent();
12453     } else
12454     if (oldGameMode == AnalyzeMode) {
12455       AnalyzeFileEvent();
12456     }
12457
12458     if(creatingBook) return TRUE;
12459     if (!matchMode && pos > 0) {
12460         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12461     } else
12462     if (matchMode || appData.timeDelay == 0) {
12463       ToEndEvent();
12464     } else if (appData.timeDelay > 0) {
12465       AutoPlayGameLoop();
12466     }
12467
12468     if (appData.debugMode)
12469         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12470
12471     loadFlag = 0; /* [HGM] true game starts */
12472     return TRUE;
12473 }
12474
12475 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12476 int
12477 ReloadPosition (int offset)
12478 {
12479     int positionNumber = lastLoadPositionNumber + offset;
12480     if (lastLoadPositionFP == NULL) {
12481         DisplayError(_("No position has been loaded yet"), 0);
12482         return FALSE;
12483     }
12484     if (positionNumber <= 0) {
12485         DisplayError(_("Can't back up any further"), 0);
12486         return FALSE;
12487     }
12488     return LoadPosition(lastLoadPositionFP, positionNumber,
12489                         lastLoadPositionTitle);
12490 }
12491
12492 /* Load the nth position from the given file */
12493 int
12494 LoadPositionFromFile (char *filename, int n, char *title)
12495 {
12496     FILE *f;
12497     char buf[MSG_SIZ];
12498
12499     if (strcmp(filename, "-") == 0) {
12500         return LoadPosition(stdin, n, "stdin");
12501     } else {
12502         f = fopen(filename, "rb");
12503         if (f == NULL) {
12504             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12505             DisplayError(buf, errno);
12506             return FALSE;
12507         } else {
12508             return LoadPosition(f, n, title);
12509         }
12510     }
12511 }
12512
12513 /* Load the nth position from the given open file, and close it */
12514 int
12515 LoadPosition (FILE *f, int positionNumber, char *title)
12516 {
12517     char *p, line[MSG_SIZ];
12518     Board initial_position;
12519     int i, j, fenMode, pn;
12520
12521     if (gameMode == Training )
12522         SetTrainingModeOff();
12523
12524     if (gameMode != BeginningOfGame) {
12525         Reset(FALSE, TRUE);
12526     }
12527     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12528         fclose(lastLoadPositionFP);
12529     }
12530     if (positionNumber == 0) positionNumber = 1;
12531     lastLoadPositionFP = f;
12532     lastLoadPositionNumber = positionNumber;
12533     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12534     if (first.pr == NoProc && !appData.noChessProgram) {
12535       StartChessProgram(&first);
12536       InitChessProgram(&first, FALSE);
12537     }
12538     pn = positionNumber;
12539     if (positionNumber < 0) {
12540         /* Negative position number means to seek to that byte offset */
12541         if (fseek(f, -positionNumber, 0) == -1) {
12542             DisplayError(_("Can't seek on position file"), 0);
12543             return FALSE;
12544         };
12545         pn = 1;
12546     } else {
12547         if (fseek(f, 0, 0) == -1) {
12548             if (f == lastLoadPositionFP ?
12549                 positionNumber == lastLoadPositionNumber + 1 :
12550                 positionNumber == 1) {
12551                 pn = 1;
12552             } else {
12553                 DisplayError(_("Can't seek on position file"), 0);
12554                 return FALSE;
12555             }
12556         }
12557     }
12558     /* See if this file is FEN or old-style xboard */
12559     if (fgets(line, MSG_SIZ, f) == NULL) {
12560         DisplayError(_("Position not found in file"), 0);
12561         return FALSE;
12562     }
12563     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12564     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12565
12566     if (pn >= 2) {
12567         if (fenMode || line[0] == '#') pn--;
12568         while (pn > 0) {
12569             /* skip positions before number pn */
12570             if (fgets(line, MSG_SIZ, f) == NULL) {
12571                 Reset(TRUE, TRUE);
12572                 DisplayError(_("Position not found in file"), 0);
12573                 return FALSE;
12574             }
12575             if (fenMode || line[0] == '#') pn--;
12576         }
12577     }
12578
12579     if (fenMode) {
12580         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12581             DisplayError(_("Bad FEN position in file"), 0);
12582             return FALSE;
12583         }
12584     } else {
12585         (void) fgets(line, MSG_SIZ, f);
12586         (void) fgets(line, MSG_SIZ, f);
12587
12588         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12589             (void) fgets(line, MSG_SIZ, f);
12590             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12591                 if (*p == ' ')
12592                   continue;
12593                 initial_position[i][j++] = CharToPiece(*p);
12594             }
12595         }
12596
12597         blackPlaysFirst = FALSE;
12598         if (!feof(f)) {
12599             (void) fgets(line, MSG_SIZ, f);
12600             if (strncmp(line, "black", strlen("black"))==0)
12601               blackPlaysFirst = TRUE;
12602         }
12603     }
12604     startedFromSetupPosition = TRUE;
12605
12606     CopyBoard(boards[0], initial_position);
12607     if (blackPlaysFirst) {
12608         currentMove = forwardMostMove = backwardMostMove = 1;
12609         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12610         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12611         CopyBoard(boards[1], initial_position);
12612         DisplayMessage("", _("Black to play"));
12613     } else {
12614         currentMove = forwardMostMove = backwardMostMove = 0;
12615         DisplayMessage("", _("White to play"));
12616     }
12617     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12618     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12619         SendToProgram("force\n", &first);
12620         SendBoard(&first, forwardMostMove);
12621     }
12622     if (appData.debugMode) {
12623 int i, j;
12624   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12625   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12626         fprintf(debugFP, "Load Position\n");
12627     }
12628
12629     if (positionNumber > 1) {
12630       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12631         DisplayTitle(line);
12632     } else {
12633         DisplayTitle(title);
12634     }
12635     gameMode = EditGame;
12636     ModeHighlight();
12637     ResetClocks();
12638     timeRemaining[0][1] = whiteTimeRemaining;
12639     timeRemaining[1][1] = blackTimeRemaining;
12640     DrawPosition(FALSE, boards[currentMove]);
12641
12642     return TRUE;
12643 }
12644
12645
12646 void
12647 CopyPlayerNameIntoFileName (char **dest, char *src)
12648 {
12649     while (*src != NULLCHAR && *src != ',') {
12650         if (*src == ' ') {
12651             *(*dest)++ = '_';
12652             src++;
12653         } else {
12654             *(*dest)++ = *src++;
12655         }
12656     }
12657 }
12658
12659 char *
12660 DefaultFileName (char *ext)
12661 {
12662     static char def[MSG_SIZ];
12663     char *p;
12664
12665     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12666         p = def;
12667         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12668         *p++ = '-';
12669         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12670         *p++ = '.';
12671         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12672     } else {
12673         def[0] = NULLCHAR;
12674     }
12675     return def;
12676 }
12677
12678 /* Save the current game to the given file */
12679 int
12680 SaveGameToFile (char *filename, int append)
12681 {
12682     FILE *f;
12683     char buf[MSG_SIZ];
12684     int result, i, t,tot=0;
12685
12686     if (strcmp(filename, "-") == 0) {
12687         return SaveGame(stdout, 0, NULL);
12688     } else {
12689         for(i=0; i<10; i++) { // upto 10 tries
12690              f = fopen(filename, append ? "a" : "w");
12691              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12692              if(f || errno != 13) break;
12693              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12694              tot += t;
12695         }
12696         if (f == NULL) {
12697             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12698             DisplayError(buf, errno);
12699             return FALSE;
12700         } else {
12701             safeStrCpy(buf, lastMsg, MSG_SIZ);
12702             DisplayMessage(_("Waiting for access to save file"), "");
12703             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12704             DisplayMessage(_("Saving game"), "");
12705             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12706             result = SaveGame(f, 0, NULL);
12707             DisplayMessage(buf, "");
12708             return result;
12709         }
12710     }
12711 }
12712
12713 char *
12714 SavePart (char *str)
12715 {
12716     static char buf[MSG_SIZ];
12717     char *p;
12718
12719     p = strchr(str, ' ');
12720     if (p == NULL) return str;
12721     strncpy(buf, str, p - str);
12722     buf[p - str] = NULLCHAR;
12723     return buf;
12724 }
12725
12726 #define PGN_MAX_LINE 75
12727
12728 #define PGN_SIDE_WHITE  0
12729 #define PGN_SIDE_BLACK  1
12730
12731 static int
12732 FindFirstMoveOutOfBook (int side)
12733 {
12734     int result = -1;
12735
12736     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12737         int index = backwardMostMove;
12738         int has_book_hit = 0;
12739
12740         if( (index % 2) != side ) {
12741             index++;
12742         }
12743
12744         while( index < forwardMostMove ) {
12745             /* Check to see if engine is in book */
12746             int depth = pvInfoList[index].depth;
12747             int score = pvInfoList[index].score;
12748             int in_book = 0;
12749
12750             if( depth <= 2 ) {
12751                 in_book = 1;
12752             }
12753             else if( score == 0 && depth == 63 ) {
12754                 in_book = 1; /* Zappa */
12755             }
12756             else if( score == 2 && depth == 99 ) {
12757                 in_book = 1; /* Abrok */
12758             }
12759
12760             has_book_hit += in_book;
12761
12762             if( ! in_book ) {
12763                 result = index;
12764
12765                 break;
12766             }
12767
12768             index += 2;
12769         }
12770     }
12771
12772     return result;
12773 }
12774
12775 void
12776 GetOutOfBookInfo (char * buf)
12777 {
12778     int oob[2];
12779     int i;
12780     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12781
12782     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12783     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12784
12785     *buf = '\0';
12786
12787     if( oob[0] >= 0 || oob[1] >= 0 ) {
12788         for( i=0; i<2; i++ ) {
12789             int idx = oob[i];
12790
12791             if( idx >= 0 ) {
12792                 if( i > 0 && oob[0] >= 0 ) {
12793                     strcat( buf, "   " );
12794                 }
12795
12796                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12797                 sprintf( buf+strlen(buf), "%s%.2f",
12798                     pvInfoList[idx].score >= 0 ? "+" : "",
12799                     pvInfoList[idx].score / 100.0 );
12800             }
12801         }
12802     }
12803 }
12804
12805 /* Save game in PGN style and close the file */
12806 int
12807 SaveGamePGN (FILE *f)
12808 {
12809     int i, offset, linelen, newblock;
12810 //    char *movetext;
12811     char numtext[32];
12812     int movelen, numlen, blank;
12813     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12814
12815     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12816
12817     PrintPGNTags(f, &gameInfo);
12818
12819     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12820
12821     if (backwardMostMove > 0 || startedFromSetupPosition) {
12822         char *fen = PositionToFEN(backwardMostMove, NULL);
12823         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12824         fprintf(f, "\n{--------------\n");
12825         PrintPosition(f, backwardMostMove);
12826         fprintf(f, "--------------}\n");
12827         free(fen);
12828     }
12829     else {
12830         /* [AS] Out of book annotation */
12831         if( appData.saveOutOfBookInfo ) {
12832             char buf[64];
12833
12834             GetOutOfBookInfo( buf );
12835
12836             if( buf[0] != '\0' ) {
12837                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12838             }
12839         }
12840
12841         fprintf(f, "\n");
12842     }
12843
12844     i = backwardMostMove;
12845     linelen = 0;
12846     newblock = TRUE;
12847
12848     while (i < forwardMostMove) {
12849         /* Print comments preceding this move */
12850         if (commentList[i] != NULL) {
12851             if (linelen > 0) fprintf(f, "\n");
12852             fprintf(f, "%s", commentList[i]);
12853             linelen = 0;
12854             newblock = TRUE;
12855         }
12856
12857         /* Format move number */
12858         if ((i % 2) == 0)
12859           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12860         else
12861           if (newblock)
12862             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12863           else
12864             numtext[0] = NULLCHAR;
12865
12866         numlen = strlen(numtext);
12867         newblock = FALSE;
12868
12869         /* Print move number */
12870         blank = linelen > 0 && numlen > 0;
12871         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12872             fprintf(f, "\n");
12873             linelen = 0;
12874             blank = 0;
12875         }
12876         if (blank) {
12877             fprintf(f, " ");
12878             linelen++;
12879         }
12880         fprintf(f, "%s", numtext);
12881         linelen += numlen;
12882
12883         /* Get move */
12884         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12885         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12886
12887         /* Print move */
12888         blank = linelen > 0 && movelen > 0;
12889         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12890             fprintf(f, "\n");
12891             linelen = 0;
12892             blank = 0;
12893         }
12894         if (blank) {
12895             fprintf(f, " ");
12896             linelen++;
12897         }
12898         fprintf(f, "%s", move_buffer);
12899         linelen += movelen;
12900
12901         /* [AS] Add PV info if present */
12902         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12903             /* [HGM] add time */
12904             char buf[MSG_SIZ]; int seconds;
12905
12906             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12907
12908             if( seconds <= 0)
12909               buf[0] = 0;
12910             else
12911               if( seconds < 30 )
12912                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12913               else
12914                 {
12915                   seconds = (seconds + 4)/10; // round to full seconds
12916                   if( seconds < 60 )
12917                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12918                   else
12919                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12920                 }
12921
12922             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12923                       pvInfoList[i].score >= 0 ? "+" : "",
12924                       pvInfoList[i].score / 100.0,
12925                       pvInfoList[i].depth,
12926                       buf );
12927
12928             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12929
12930             /* Print score/depth */
12931             blank = linelen > 0 && movelen > 0;
12932             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12933                 fprintf(f, "\n");
12934                 linelen = 0;
12935                 blank = 0;
12936             }
12937             if (blank) {
12938                 fprintf(f, " ");
12939                 linelen++;
12940             }
12941             fprintf(f, "%s", move_buffer);
12942             linelen += movelen;
12943         }
12944
12945         i++;
12946     }
12947
12948     /* Start a new line */
12949     if (linelen > 0) fprintf(f, "\n");
12950
12951     /* Print comments after last move */
12952     if (commentList[i] != NULL) {
12953         fprintf(f, "%s\n", commentList[i]);
12954     }
12955
12956     /* Print result */
12957     if (gameInfo.resultDetails != NULL &&
12958         gameInfo.resultDetails[0] != NULLCHAR) {
12959         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12960                 PGNResult(gameInfo.result));
12961     } else {
12962         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12963     }
12964
12965     fclose(f);
12966     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12967     return TRUE;
12968 }
12969
12970 /* Save game in old style and close the file */
12971 int
12972 SaveGameOldStyle (FILE *f)
12973 {
12974     int i, offset;
12975     time_t tm;
12976
12977     tm = time((time_t *) NULL);
12978
12979     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12980     PrintOpponents(f);
12981
12982     if (backwardMostMove > 0 || startedFromSetupPosition) {
12983         fprintf(f, "\n[--------------\n");
12984         PrintPosition(f, backwardMostMove);
12985         fprintf(f, "--------------]\n");
12986     } else {
12987         fprintf(f, "\n");
12988     }
12989
12990     i = backwardMostMove;
12991     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12992
12993     while (i < forwardMostMove) {
12994         if (commentList[i] != NULL) {
12995             fprintf(f, "[%s]\n", commentList[i]);
12996         }
12997
12998         if ((i % 2) == 1) {
12999             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13000             i++;
13001         } else {
13002             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13003             i++;
13004             if (commentList[i] != NULL) {
13005                 fprintf(f, "\n");
13006                 continue;
13007             }
13008             if (i >= forwardMostMove) {
13009                 fprintf(f, "\n");
13010                 break;
13011             }
13012             fprintf(f, "%s\n", parseList[i]);
13013             i++;
13014         }
13015     }
13016
13017     if (commentList[i] != NULL) {
13018         fprintf(f, "[%s]\n", commentList[i]);
13019     }
13020
13021     /* This isn't really the old style, but it's close enough */
13022     if (gameInfo.resultDetails != NULL &&
13023         gameInfo.resultDetails[0] != NULLCHAR) {
13024         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13025                 gameInfo.resultDetails);
13026     } else {
13027         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13028     }
13029
13030     fclose(f);
13031     return TRUE;
13032 }
13033
13034 /* Save the current game to open file f and close the file */
13035 int
13036 SaveGame (FILE *f, int dummy, char *dummy2)
13037 {
13038     if (gameMode == EditPosition) EditPositionDone(TRUE);
13039     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13040     if (appData.oldSaveStyle)
13041       return SaveGameOldStyle(f);
13042     else
13043       return SaveGamePGN(f);
13044 }
13045
13046 /* Save the current position to the given file */
13047 int
13048 SavePositionToFile (char *filename)
13049 {
13050     FILE *f;
13051     char buf[MSG_SIZ];
13052
13053     if (strcmp(filename, "-") == 0) {
13054         return SavePosition(stdout, 0, NULL);
13055     } else {
13056         f = fopen(filename, "a");
13057         if (f == NULL) {
13058             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13059             DisplayError(buf, errno);
13060             return FALSE;
13061         } else {
13062             safeStrCpy(buf, lastMsg, MSG_SIZ);
13063             DisplayMessage(_("Waiting for access to save file"), "");
13064             flock(fileno(f), LOCK_EX); // [HGM] lock
13065             DisplayMessage(_("Saving position"), "");
13066             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13067             SavePosition(f, 0, NULL);
13068             DisplayMessage(buf, "");
13069             return TRUE;
13070         }
13071     }
13072 }
13073
13074 /* Save the current position to the given open file and close the file */
13075 int
13076 SavePosition (FILE *f, int dummy, char *dummy2)
13077 {
13078     time_t tm;
13079     char *fen;
13080
13081     if (gameMode == EditPosition) EditPositionDone(TRUE);
13082     if (appData.oldSaveStyle) {
13083         tm = time((time_t *) NULL);
13084
13085         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13086         PrintOpponents(f);
13087         fprintf(f, "[--------------\n");
13088         PrintPosition(f, currentMove);
13089         fprintf(f, "--------------]\n");
13090     } else {
13091         fen = PositionToFEN(currentMove, NULL);
13092         fprintf(f, "%s\n", fen);
13093         free(fen);
13094     }
13095     fclose(f);
13096     return TRUE;
13097 }
13098
13099 void
13100 ReloadCmailMsgEvent (int unregister)
13101 {
13102 #if !WIN32
13103     static char *inFilename = NULL;
13104     static char *outFilename;
13105     int i;
13106     struct stat inbuf, outbuf;
13107     int status;
13108
13109     /* Any registered moves are unregistered if unregister is set, */
13110     /* i.e. invoked by the signal handler */
13111     if (unregister) {
13112         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13113             cmailMoveRegistered[i] = FALSE;
13114             if (cmailCommentList[i] != NULL) {
13115                 free(cmailCommentList[i]);
13116                 cmailCommentList[i] = NULL;
13117             }
13118         }
13119         nCmailMovesRegistered = 0;
13120     }
13121
13122     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13123         cmailResult[i] = CMAIL_NOT_RESULT;
13124     }
13125     nCmailResults = 0;
13126
13127     if (inFilename == NULL) {
13128         /* Because the filenames are static they only get malloced once  */
13129         /* and they never get freed                                      */
13130         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13131         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13132
13133         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13134         sprintf(outFilename, "%s.out", appData.cmailGameName);
13135     }
13136
13137     status = stat(outFilename, &outbuf);
13138     if (status < 0) {
13139         cmailMailedMove = FALSE;
13140     } else {
13141         status = stat(inFilename, &inbuf);
13142         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13143     }
13144
13145     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13146        counts the games, notes how each one terminated, etc.
13147
13148        It would be nice to remove this kludge and instead gather all
13149        the information while building the game list.  (And to keep it
13150        in the game list nodes instead of having a bunch of fixed-size
13151        parallel arrays.)  Note this will require getting each game's
13152        termination from the PGN tags, as the game list builder does
13153        not process the game moves.  --mann
13154        */
13155     cmailMsgLoaded = TRUE;
13156     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13157
13158     /* Load first game in the file or popup game menu */
13159     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13160
13161 #endif /* !WIN32 */
13162     return;
13163 }
13164
13165 int
13166 RegisterMove ()
13167 {
13168     FILE *f;
13169     char string[MSG_SIZ];
13170
13171     if (   cmailMailedMove
13172         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13173         return TRUE;            /* Allow free viewing  */
13174     }
13175
13176     /* Unregister move to ensure that we don't leave RegisterMove        */
13177     /* with the move registered when the conditions for registering no   */
13178     /* longer hold                                                       */
13179     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13180         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13181         nCmailMovesRegistered --;
13182
13183         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13184           {
13185               free(cmailCommentList[lastLoadGameNumber - 1]);
13186               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13187           }
13188     }
13189
13190     if (cmailOldMove == -1) {
13191         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13192         return FALSE;
13193     }
13194
13195     if (currentMove > cmailOldMove + 1) {
13196         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13197         return FALSE;
13198     }
13199
13200     if (currentMove < cmailOldMove) {
13201         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13202         return FALSE;
13203     }
13204
13205     if (forwardMostMove > currentMove) {
13206         /* Silently truncate extra moves */
13207         TruncateGame();
13208     }
13209
13210     if (   (currentMove == cmailOldMove + 1)
13211         || (   (currentMove == cmailOldMove)
13212             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13213                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13214         if (gameInfo.result != GameUnfinished) {
13215             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13216         }
13217
13218         if (commentList[currentMove] != NULL) {
13219             cmailCommentList[lastLoadGameNumber - 1]
13220               = StrSave(commentList[currentMove]);
13221         }
13222         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13223
13224         if (appData.debugMode)
13225           fprintf(debugFP, "Saving %s for game %d\n",
13226                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13227
13228         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13229
13230         f = fopen(string, "w");
13231         if (appData.oldSaveStyle) {
13232             SaveGameOldStyle(f); /* also closes the file */
13233
13234             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13235             f = fopen(string, "w");
13236             SavePosition(f, 0, NULL); /* also closes the file */
13237         } else {
13238             fprintf(f, "{--------------\n");
13239             PrintPosition(f, currentMove);
13240             fprintf(f, "--------------}\n\n");
13241
13242             SaveGame(f, 0, NULL); /* also closes the file*/
13243         }
13244
13245         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13246         nCmailMovesRegistered ++;
13247     } else if (nCmailGames == 1) {
13248         DisplayError(_("You have not made a move yet"), 0);
13249         return FALSE;
13250     }
13251
13252     return TRUE;
13253 }
13254
13255 void
13256 MailMoveEvent ()
13257 {
13258 #if !WIN32
13259     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13260     FILE *commandOutput;
13261     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13262     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13263     int nBuffers;
13264     int i;
13265     int archived;
13266     char *arcDir;
13267
13268     if (! cmailMsgLoaded) {
13269         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13270         return;
13271     }
13272
13273     if (nCmailGames == nCmailResults) {
13274         DisplayError(_("No unfinished games"), 0);
13275         return;
13276     }
13277
13278 #if CMAIL_PROHIBIT_REMAIL
13279     if (cmailMailedMove) {
13280       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);
13281         DisplayError(msg, 0);
13282         return;
13283     }
13284 #endif
13285
13286     if (! (cmailMailedMove || RegisterMove())) return;
13287
13288     if (   cmailMailedMove
13289         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13290       snprintf(string, MSG_SIZ, partCommandString,
13291                appData.debugMode ? " -v" : "", appData.cmailGameName);
13292         commandOutput = popen(string, "r");
13293
13294         if (commandOutput == NULL) {
13295             DisplayError(_("Failed to invoke cmail"), 0);
13296         } else {
13297             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13298                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13299             }
13300             if (nBuffers > 1) {
13301                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13302                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13303                 nBytes = MSG_SIZ - 1;
13304             } else {
13305                 (void) memcpy(msg, buffer, nBytes);
13306             }
13307             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13308
13309             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13310                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13311
13312                 archived = TRUE;
13313                 for (i = 0; i < nCmailGames; i ++) {
13314                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13315                         archived = FALSE;
13316                     }
13317                 }
13318                 if (   archived
13319                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13320                         != NULL)) {
13321                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13322                            arcDir,
13323                            appData.cmailGameName,
13324                            gameInfo.date);
13325                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13326                     cmailMsgLoaded = FALSE;
13327                 }
13328             }
13329
13330             DisplayInformation(msg);
13331             pclose(commandOutput);
13332         }
13333     } else {
13334         if ((*cmailMsg) != '\0') {
13335             DisplayInformation(cmailMsg);
13336         }
13337     }
13338
13339     return;
13340 #endif /* !WIN32 */
13341 }
13342
13343 char *
13344 CmailMsg ()
13345 {
13346 #if WIN32
13347     return NULL;
13348 #else
13349     int  prependComma = 0;
13350     char number[5];
13351     char string[MSG_SIZ];       /* Space for game-list */
13352     int  i;
13353
13354     if (!cmailMsgLoaded) return "";
13355
13356     if (cmailMailedMove) {
13357       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13358     } else {
13359         /* Create a list of games left */
13360       snprintf(string, MSG_SIZ, "[");
13361         for (i = 0; i < nCmailGames; i ++) {
13362             if (! (   cmailMoveRegistered[i]
13363                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13364                 if (prependComma) {
13365                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13366                 } else {
13367                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13368                     prependComma = 1;
13369                 }
13370
13371                 strcat(string, number);
13372             }
13373         }
13374         strcat(string, "]");
13375
13376         if (nCmailMovesRegistered + nCmailResults == 0) {
13377             switch (nCmailGames) {
13378               case 1:
13379                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13380                 break;
13381
13382               case 2:
13383                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13384                 break;
13385
13386               default:
13387                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13388                          nCmailGames);
13389                 break;
13390             }
13391         } else {
13392             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13393               case 1:
13394                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13395                          string);
13396                 break;
13397
13398               case 0:
13399                 if (nCmailResults == nCmailGames) {
13400                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13401                 } else {
13402                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13403                 }
13404                 break;
13405
13406               default:
13407                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13408                          string);
13409             }
13410         }
13411     }
13412     return cmailMsg;
13413 #endif /* WIN32 */
13414 }
13415
13416 void
13417 ResetGameEvent ()
13418 {
13419     if (gameMode == Training)
13420       SetTrainingModeOff();
13421
13422     Reset(TRUE, TRUE);
13423     cmailMsgLoaded = FALSE;
13424     if (appData.icsActive) {
13425       SendToICS(ics_prefix);
13426       SendToICS("refresh\n");
13427     }
13428 }
13429
13430 void
13431 ExitEvent (int status)
13432 {
13433     exiting++;
13434     if (exiting > 2) {
13435       /* Give up on clean exit */
13436       exit(status);
13437     }
13438     if (exiting > 1) {
13439       /* Keep trying for clean exit */
13440       return;
13441     }
13442
13443     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13444
13445     if (telnetISR != NULL) {
13446       RemoveInputSource(telnetISR);
13447     }
13448     if (icsPR != NoProc) {
13449       DestroyChildProcess(icsPR, TRUE);
13450     }
13451
13452     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13453     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13454
13455     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13456     /* make sure this other one finishes before killing it!                  */
13457     if(endingGame) { int count = 0;
13458         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13459         while(endingGame && count++ < 10) DoSleep(1);
13460         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13461     }
13462
13463     /* Kill off chess programs */
13464     if (first.pr != NoProc) {
13465         ExitAnalyzeMode();
13466
13467         DoSleep( appData.delayBeforeQuit );
13468         SendToProgram("quit\n", &first);
13469         DoSleep( appData.delayAfterQuit );
13470         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13471     }
13472     if (second.pr != NoProc) {
13473         DoSleep( appData.delayBeforeQuit );
13474         SendToProgram("quit\n", &second);
13475         DoSleep( appData.delayAfterQuit );
13476         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13477     }
13478     if (first.isr != NULL) {
13479         RemoveInputSource(first.isr);
13480     }
13481     if (second.isr != NULL) {
13482         RemoveInputSource(second.isr);
13483     }
13484
13485     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13486     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13487
13488     ShutDownFrontEnd();
13489     exit(status);
13490 }
13491
13492 void
13493 PauseEngine (ChessProgramState *cps)
13494 {
13495     SendToProgram("pause\n", cps);
13496     cps->pause = 2;
13497 }
13498
13499 void
13500 UnPauseEngine (ChessProgramState *cps)
13501 {
13502     SendToProgram("resume\n", cps);
13503     cps->pause = 1;
13504 }
13505
13506 void
13507 PauseEvent ()
13508 {
13509     if (appData.debugMode)
13510         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13511     if (pausing) {
13512         pausing = FALSE;
13513         ModeHighlight();
13514         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13515             StartClocks();
13516             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13517                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13518                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13519             }
13520             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13521             HandleMachineMove(stashedInputMove, stalledEngine);
13522             stalledEngine = NULL;
13523             return;
13524         }
13525         if (gameMode == MachinePlaysWhite ||
13526             gameMode == TwoMachinesPlay   ||
13527             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13528             if(first.pause)  UnPauseEngine(&first);
13529             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13530             if(second.pause) UnPauseEngine(&second);
13531             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13532             StartClocks();
13533         } else {
13534             DisplayBothClocks();
13535         }
13536         if (gameMode == PlayFromGameFile) {
13537             if (appData.timeDelay >= 0)
13538                 AutoPlayGameLoop();
13539         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13540             Reset(FALSE, TRUE);
13541             SendToICS(ics_prefix);
13542             SendToICS("refresh\n");
13543         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13544             ForwardInner(forwardMostMove);
13545         }
13546         pauseExamInvalid = FALSE;
13547     } else {
13548         switch (gameMode) {
13549           default:
13550             return;
13551           case IcsExamining:
13552             pauseExamForwardMostMove = forwardMostMove;
13553             pauseExamInvalid = FALSE;
13554             /* fall through */
13555           case IcsObserving:
13556           case IcsPlayingWhite:
13557           case IcsPlayingBlack:
13558             pausing = TRUE;
13559             ModeHighlight();
13560             return;
13561           case PlayFromGameFile:
13562             (void) StopLoadGameTimer();
13563             pausing = TRUE;
13564             ModeHighlight();
13565             break;
13566           case BeginningOfGame:
13567             if (appData.icsActive) return;
13568             /* else fall through */
13569           case MachinePlaysWhite:
13570           case MachinePlaysBlack:
13571           case TwoMachinesPlay:
13572             if (forwardMostMove == 0)
13573               return;           /* don't pause if no one has moved */
13574             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13575                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13576                 if(onMove->pause) {           // thinking engine can be paused
13577                     PauseEngine(onMove);      // do it
13578                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13579                         PauseEngine(onMove->other);
13580                     else
13581                         SendToProgram("easy\n", onMove->other);
13582                     StopClocks();
13583                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13584             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13585                 if(first.pause) {
13586                     PauseEngine(&first);
13587                     StopClocks();
13588                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13589             } else { // human on move, pause pondering by either method
13590                 if(first.pause)
13591                     PauseEngine(&first);
13592                 else if(appData.ponderNextMove)
13593                     SendToProgram("easy\n", &first);
13594                 StopClocks();
13595             }
13596             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13597           case AnalyzeMode:
13598             pausing = TRUE;
13599             ModeHighlight();
13600             break;
13601         }
13602     }
13603 }
13604
13605 void
13606 EditCommentEvent ()
13607 {
13608     char title[MSG_SIZ];
13609
13610     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13611       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13612     } else {
13613       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13614                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13615                parseList[currentMove - 1]);
13616     }
13617
13618     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13619 }
13620
13621
13622 void
13623 EditTagsEvent ()
13624 {
13625     char *tags = PGNTags(&gameInfo);
13626     bookUp = FALSE;
13627     EditTagsPopUp(tags, NULL);
13628     free(tags);
13629 }
13630
13631 void
13632 ToggleSecond ()
13633 {
13634   if(second.analyzing) {
13635     SendToProgram("exit\n", &second);
13636     second.analyzing = FALSE;
13637   } else {
13638     if (second.pr == NoProc) StartChessProgram(&second);
13639     InitChessProgram(&second, FALSE);
13640     FeedMovesToProgram(&second, currentMove);
13641
13642     SendToProgram("analyze\n", &second);
13643     second.analyzing = TRUE;
13644   }
13645 }
13646
13647 /* Toggle ShowThinking */
13648 void
13649 ToggleShowThinking()
13650 {
13651   appData.showThinking = !appData.showThinking;
13652   ShowThinkingEvent();
13653 }
13654
13655 int
13656 AnalyzeModeEvent ()
13657 {
13658     char buf[MSG_SIZ];
13659
13660     if (!first.analysisSupport) {
13661       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13662       DisplayError(buf, 0);
13663       return 0;
13664     }
13665     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13666     if (appData.icsActive) {
13667         if (gameMode != IcsObserving) {
13668           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13669             DisplayError(buf, 0);
13670             /* secure check */
13671             if (appData.icsEngineAnalyze) {
13672                 if (appData.debugMode)
13673                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13674                 ExitAnalyzeMode();
13675                 ModeHighlight();
13676             }
13677             return 0;
13678         }
13679         /* if enable, user wants to disable icsEngineAnalyze */
13680         if (appData.icsEngineAnalyze) {
13681                 ExitAnalyzeMode();
13682                 ModeHighlight();
13683                 return 0;
13684         }
13685         appData.icsEngineAnalyze = TRUE;
13686         if (appData.debugMode)
13687             fprintf(debugFP, "ICS engine analyze starting... \n");
13688     }
13689
13690     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13691     if (appData.noChessProgram || gameMode == AnalyzeMode)
13692       return 0;
13693
13694     if (gameMode != AnalyzeFile) {
13695         if (!appData.icsEngineAnalyze) {
13696                EditGameEvent();
13697                if (gameMode != EditGame) return 0;
13698         }
13699         if (!appData.showThinking) ToggleShowThinking();
13700         ResurrectChessProgram();
13701         SendToProgram("analyze\n", &first);
13702         first.analyzing = TRUE;
13703         /*first.maybeThinking = TRUE;*/
13704         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13705         EngineOutputPopUp();
13706     }
13707     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13708     pausing = FALSE;
13709     ModeHighlight();
13710     SetGameInfo();
13711
13712     StartAnalysisClock();
13713     GetTimeMark(&lastNodeCountTime);
13714     lastNodeCount = 0;
13715     return 1;
13716 }
13717
13718 void
13719 AnalyzeFileEvent ()
13720 {
13721     if (appData.noChessProgram || gameMode == AnalyzeFile)
13722       return;
13723
13724     if (!first.analysisSupport) {
13725       char buf[MSG_SIZ];
13726       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13727       DisplayError(buf, 0);
13728       return;
13729     }
13730
13731     if (gameMode != AnalyzeMode) {
13732         keepInfo = 1; // mere annotating should not alter PGN tags
13733         EditGameEvent();
13734         keepInfo = 0;
13735         if (gameMode != EditGame) return;
13736         if (!appData.showThinking) ToggleShowThinking();
13737         ResurrectChessProgram();
13738         SendToProgram("analyze\n", &first);
13739         first.analyzing = TRUE;
13740         /*first.maybeThinking = TRUE;*/
13741         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13742         EngineOutputPopUp();
13743     }
13744     gameMode = AnalyzeFile;
13745     pausing = FALSE;
13746     ModeHighlight();
13747
13748     StartAnalysisClock();
13749     GetTimeMark(&lastNodeCountTime);
13750     lastNodeCount = 0;
13751     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13752     AnalysisPeriodicEvent(1);
13753 }
13754
13755 void
13756 MachineWhiteEvent ()
13757 {
13758     char buf[MSG_SIZ];
13759     char *bookHit = NULL;
13760
13761     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13762       return;
13763
13764
13765     if (gameMode == PlayFromGameFile ||
13766         gameMode == TwoMachinesPlay  ||
13767         gameMode == Training         ||
13768         gameMode == AnalyzeMode      ||
13769         gameMode == EndOfGame)
13770         EditGameEvent();
13771
13772     if (gameMode == EditPosition)
13773         EditPositionDone(TRUE);
13774
13775     if (!WhiteOnMove(currentMove)) {
13776         DisplayError(_("It is not White's turn"), 0);
13777         return;
13778     }
13779
13780     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13781       ExitAnalyzeMode();
13782
13783     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13784         gameMode == AnalyzeFile)
13785         TruncateGame();
13786
13787     ResurrectChessProgram();    /* in case it isn't running */
13788     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13789         gameMode = MachinePlaysWhite;
13790         ResetClocks();
13791     } else
13792     gameMode = MachinePlaysWhite;
13793     pausing = FALSE;
13794     ModeHighlight();
13795     SetGameInfo();
13796     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13797     DisplayTitle(buf);
13798     if (first.sendName) {
13799       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13800       SendToProgram(buf, &first);
13801     }
13802     if (first.sendTime) {
13803       if (first.useColors) {
13804         SendToProgram("black\n", &first); /*gnu kludge*/
13805       }
13806       SendTimeRemaining(&first, TRUE);
13807     }
13808     if (first.useColors) {
13809       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13810     }
13811     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13812     SetMachineThinkingEnables();
13813     first.maybeThinking = TRUE;
13814     StartClocks();
13815     firstMove = FALSE;
13816
13817     if (appData.autoFlipView && !flipView) {
13818       flipView = !flipView;
13819       DrawPosition(FALSE, NULL);
13820       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13821     }
13822
13823     if(bookHit) { // [HGM] book: simulate book reply
13824         static char bookMove[MSG_SIZ]; // a bit generous?
13825
13826         programStats.nodes = programStats.depth = programStats.time =
13827         programStats.score = programStats.got_only_move = 0;
13828         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13829
13830         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13831         strcat(bookMove, bookHit);
13832         HandleMachineMove(bookMove, &first);
13833     }
13834 }
13835
13836 void
13837 MachineBlackEvent ()
13838 {
13839   char buf[MSG_SIZ];
13840   char *bookHit = NULL;
13841
13842     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13843         return;
13844
13845
13846     if (gameMode == PlayFromGameFile ||
13847         gameMode == TwoMachinesPlay  ||
13848         gameMode == Training         ||
13849         gameMode == AnalyzeMode      ||
13850         gameMode == EndOfGame)
13851         EditGameEvent();
13852
13853     if (gameMode == EditPosition)
13854         EditPositionDone(TRUE);
13855
13856     if (WhiteOnMove(currentMove)) {
13857         DisplayError(_("It is not Black's turn"), 0);
13858         return;
13859     }
13860
13861     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13862       ExitAnalyzeMode();
13863
13864     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13865         gameMode == AnalyzeFile)
13866         TruncateGame();
13867
13868     ResurrectChessProgram();    /* in case it isn't running */
13869     gameMode = MachinePlaysBlack;
13870     pausing = FALSE;
13871     ModeHighlight();
13872     SetGameInfo();
13873     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13874     DisplayTitle(buf);
13875     if (first.sendName) {
13876       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13877       SendToProgram(buf, &first);
13878     }
13879     if (first.sendTime) {
13880       if (first.useColors) {
13881         SendToProgram("white\n", &first); /*gnu kludge*/
13882       }
13883       SendTimeRemaining(&first, FALSE);
13884     }
13885     if (first.useColors) {
13886       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13887     }
13888     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13889     SetMachineThinkingEnables();
13890     first.maybeThinking = TRUE;
13891     StartClocks();
13892
13893     if (appData.autoFlipView && flipView) {
13894       flipView = !flipView;
13895       DrawPosition(FALSE, NULL);
13896       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13897     }
13898     if(bookHit) { // [HGM] book: simulate book reply
13899         static char bookMove[MSG_SIZ]; // a bit generous?
13900
13901         programStats.nodes = programStats.depth = programStats.time =
13902         programStats.score = programStats.got_only_move = 0;
13903         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13904
13905         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13906         strcat(bookMove, bookHit);
13907         HandleMachineMove(bookMove, &first);
13908     }
13909 }
13910
13911
13912 void
13913 DisplayTwoMachinesTitle ()
13914 {
13915     char buf[MSG_SIZ];
13916     if (appData.matchGames > 0) {
13917         if(appData.tourneyFile[0]) {
13918           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13919                    gameInfo.white, _("vs."), gameInfo.black,
13920                    nextGame+1, appData.matchGames+1,
13921                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13922         } else
13923         if (first.twoMachinesColor[0] == 'w') {
13924           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13925                    gameInfo.white, _("vs."),  gameInfo.black,
13926                    first.matchWins, second.matchWins,
13927                    matchGame - 1 - (first.matchWins + second.matchWins));
13928         } else {
13929           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13930                    gameInfo.white, _("vs."), gameInfo.black,
13931                    second.matchWins, first.matchWins,
13932                    matchGame - 1 - (first.matchWins + second.matchWins));
13933         }
13934     } else {
13935       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13936     }
13937     DisplayTitle(buf);
13938 }
13939
13940 void
13941 SettingsMenuIfReady ()
13942 {
13943   if (second.lastPing != second.lastPong) {
13944     DisplayMessage("", _("Waiting for second chess program"));
13945     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13946     return;
13947   }
13948   ThawUI();
13949   DisplayMessage("", "");
13950   SettingsPopUp(&second);
13951 }
13952
13953 int
13954 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13955 {
13956     char buf[MSG_SIZ];
13957     if (cps->pr == NoProc) {
13958         StartChessProgram(cps);
13959         if (cps->protocolVersion == 1) {
13960           retry();
13961           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
13962         } else {
13963           /* kludge: allow timeout for initial "feature" command */
13964           if(retry != TwoMachinesEventIfReady) FreezeUI();
13965           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13966           DisplayMessage("", buf);
13967           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13968         }
13969         return 1;
13970     }
13971     return 0;
13972 }
13973
13974 void
13975 TwoMachinesEvent P((void))
13976 {
13977     int i;
13978     char buf[MSG_SIZ];
13979     ChessProgramState *onmove;
13980     char *bookHit = NULL;
13981     static int stalling = 0;
13982     TimeMark now;
13983     long wait;
13984
13985     if (appData.noChessProgram) return;
13986
13987     switch (gameMode) {
13988       case TwoMachinesPlay:
13989         return;
13990       case MachinePlaysWhite:
13991       case MachinePlaysBlack:
13992         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13993             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13994             return;
13995         }
13996         /* fall through */
13997       case BeginningOfGame:
13998       case PlayFromGameFile:
13999       case EndOfGame:
14000         EditGameEvent();
14001         if (gameMode != EditGame) return;
14002         break;
14003       case EditPosition:
14004         EditPositionDone(TRUE);
14005         break;
14006       case AnalyzeMode:
14007       case AnalyzeFile:
14008         ExitAnalyzeMode();
14009         break;
14010       case EditGame:
14011       default:
14012         break;
14013     }
14014
14015 //    forwardMostMove = currentMove;
14016     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14017     startingEngine = TRUE;
14018
14019     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14020
14021     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14022     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14023       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14024       return;
14025     }
14026     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14027
14028     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14029         startingEngine = FALSE;
14030         DisplayError("second engine does not play this", 0);
14031         return;
14032     }
14033
14034     if(!stalling) {
14035       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14036       SendToProgram("force\n", &second);
14037       stalling = 1;
14038       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14039       return;
14040     }
14041     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14042     if(appData.matchPause>10000 || appData.matchPause<10)
14043                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14044     wait = SubtractTimeMarks(&now, &pauseStart);
14045     if(wait < appData.matchPause) {
14046         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14047         return;
14048     }
14049     // we are now committed to starting the game
14050     stalling = 0;
14051     DisplayMessage("", "");
14052     if (startedFromSetupPosition) {
14053         SendBoard(&second, backwardMostMove);
14054     if (appData.debugMode) {
14055         fprintf(debugFP, "Two Machines\n");
14056     }
14057     }
14058     for (i = backwardMostMove; i < forwardMostMove; i++) {
14059         SendMoveToProgram(i, &second);
14060     }
14061
14062     gameMode = TwoMachinesPlay;
14063     pausing = startingEngine = FALSE;
14064     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14065     SetGameInfo();
14066     DisplayTwoMachinesTitle();
14067     firstMove = TRUE;
14068     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14069         onmove = &first;
14070     } else {
14071         onmove = &second;
14072     }
14073     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14074     SendToProgram(first.computerString, &first);
14075     if (first.sendName) {
14076       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14077       SendToProgram(buf, &first);
14078     }
14079     SendToProgram(second.computerString, &second);
14080     if (second.sendName) {
14081       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14082       SendToProgram(buf, &second);
14083     }
14084
14085     ResetClocks();
14086     if (!first.sendTime || !second.sendTime) {
14087         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14088         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14089     }
14090     if (onmove->sendTime) {
14091       if (onmove->useColors) {
14092         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14093       }
14094       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14095     }
14096     if (onmove->useColors) {
14097       SendToProgram(onmove->twoMachinesColor, onmove);
14098     }
14099     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14100 //    SendToProgram("go\n", onmove);
14101     onmove->maybeThinking = TRUE;
14102     SetMachineThinkingEnables();
14103
14104     StartClocks();
14105
14106     if(bookHit) { // [HGM] book: simulate book reply
14107         static char bookMove[MSG_SIZ]; // a bit generous?
14108
14109         programStats.nodes = programStats.depth = programStats.time =
14110         programStats.score = programStats.got_only_move = 0;
14111         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14112
14113         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14114         strcat(bookMove, bookHit);
14115         savedMessage = bookMove; // args for deferred call
14116         savedState = onmove;
14117         ScheduleDelayedEvent(DeferredBookMove, 1);
14118     }
14119 }
14120
14121 void
14122 TrainingEvent ()
14123 {
14124     if (gameMode == Training) {
14125       SetTrainingModeOff();
14126       gameMode = PlayFromGameFile;
14127       DisplayMessage("", _("Training mode off"));
14128     } else {
14129       gameMode = Training;
14130       animateTraining = appData.animate;
14131
14132       /* make sure we are not already at the end of the game */
14133       if (currentMove < forwardMostMove) {
14134         SetTrainingModeOn();
14135         DisplayMessage("", _("Training mode on"));
14136       } else {
14137         gameMode = PlayFromGameFile;
14138         DisplayError(_("Already at end of game"), 0);
14139       }
14140     }
14141     ModeHighlight();
14142 }
14143
14144 void
14145 IcsClientEvent ()
14146 {
14147     if (!appData.icsActive) return;
14148     switch (gameMode) {
14149       case IcsPlayingWhite:
14150       case IcsPlayingBlack:
14151       case IcsObserving:
14152       case IcsIdle:
14153       case BeginningOfGame:
14154       case IcsExamining:
14155         return;
14156
14157       case EditGame:
14158         break;
14159
14160       case EditPosition:
14161         EditPositionDone(TRUE);
14162         break;
14163
14164       case AnalyzeMode:
14165       case AnalyzeFile:
14166         ExitAnalyzeMode();
14167         break;
14168
14169       default:
14170         EditGameEvent();
14171         break;
14172     }
14173
14174     gameMode = IcsIdle;
14175     ModeHighlight();
14176     return;
14177 }
14178
14179 void
14180 EditGameEvent ()
14181 {
14182     int i;
14183
14184     switch (gameMode) {
14185       case Training:
14186         SetTrainingModeOff();
14187         break;
14188       case MachinePlaysWhite:
14189       case MachinePlaysBlack:
14190       case BeginningOfGame:
14191         SendToProgram("force\n", &first);
14192         SetUserThinkingEnables();
14193         break;
14194       case PlayFromGameFile:
14195         (void) StopLoadGameTimer();
14196         if (gameFileFP != NULL) {
14197             gameFileFP = NULL;
14198         }
14199         break;
14200       case EditPosition:
14201         EditPositionDone(TRUE);
14202         break;
14203       case AnalyzeMode:
14204       case AnalyzeFile:
14205         ExitAnalyzeMode();
14206         SendToProgram("force\n", &first);
14207         break;
14208       case TwoMachinesPlay:
14209         GameEnds(EndOfFile, NULL, GE_PLAYER);
14210         ResurrectChessProgram();
14211         SetUserThinkingEnables();
14212         break;
14213       case EndOfGame:
14214         ResurrectChessProgram();
14215         break;
14216       case IcsPlayingBlack:
14217       case IcsPlayingWhite:
14218         DisplayError(_("Warning: You are still playing a game"), 0);
14219         break;
14220       case IcsObserving:
14221         DisplayError(_("Warning: You are still observing a game"), 0);
14222         break;
14223       case IcsExamining:
14224         DisplayError(_("Warning: You are still examining a game"), 0);
14225         break;
14226       case IcsIdle:
14227         break;
14228       case EditGame:
14229       default:
14230         return;
14231     }
14232
14233     pausing = FALSE;
14234     StopClocks();
14235     first.offeredDraw = second.offeredDraw = 0;
14236
14237     if (gameMode == PlayFromGameFile) {
14238         whiteTimeRemaining = timeRemaining[0][currentMove];
14239         blackTimeRemaining = timeRemaining[1][currentMove];
14240         DisplayTitle("");
14241     }
14242
14243     if (gameMode == MachinePlaysWhite ||
14244         gameMode == MachinePlaysBlack ||
14245         gameMode == TwoMachinesPlay ||
14246         gameMode == EndOfGame) {
14247         i = forwardMostMove;
14248         while (i > currentMove) {
14249             SendToProgram("undo\n", &first);
14250             i--;
14251         }
14252         if(!adjustedClock) {
14253         whiteTimeRemaining = timeRemaining[0][currentMove];
14254         blackTimeRemaining = timeRemaining[1][currentMove];
14255         DisplayBothClocks();
14256         }
14257         if (whiteFlag || blackFlag) {
14258             whiteFlag = blackFlag = 0;
14259         }
14260         DisplayTitle("");
14261     }
14262
14263     gameMode = EditGame;
14264     ModeHighlight();
14265     SetGameInfo();
14266 }
14267
14268
14269 void
14270 EditPositionEvent ()
14271 {
14272     if (gameMode == EditPosition) {
14273         EditGameEvent();
14274         return;
14275     }
14276
14277     EditGameEvent();
14278     if (gameMode != EditGame) return;
14279
14280     gameMode = EditPosition;
14281     ModeHighlight();
14282     SetGameInfo();
14283     if (currentMove > 0)
14284       CopyBoard(boards[0], boards[currentMove]);
14285
14286     blackPlaysFirst = !WhiteOnMove(currentMove);
14287     ResetClocks();
14288     currentMove = forwardMostMove = backwardMostMove = 0;
14289     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14290     DisplayMove(-1);
14291     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14292 }
14293
14294 void
14295 ExitAnalyzeMode ()
14296 {
14297     /* [DM] icsEngineAnalyze - possible call from other functions */
14298     if (appData.icsEngineAnalyze) {
14299         appData.icsEngineAnalyze = FALSE;
14300
14301         DisplayMessage("",_("Close ICS engine analyze..."));
14302     }
14303     if (first.analysisSupport && first.analyzing) {
14304       SendToBoth("exit\n");
14305       first.analyzing = second.analyzing = FALSE;
14306     }
14307     thinkOutput[0] = NULLCHAR;
14308 }
14309
14310 void
14311 EditPositionDone (Boolean fakeRights)
14312 {
14313     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14314
14315     startedFromSetupPosition = TRUE;
14316     InitChessProgram(&first, FALSE);
14317     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14318       boards[0][EP_STATUS] = EP_NONE;
14319       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14320       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14321         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14322         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14323       } else boards[0][CASTLING][2] = NoRights;
14324       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14325         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14326         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14327       } else boards[0][CASTLING][5] = NoRights;
14328       if(gameInfo.variant == VariantSChess) {
14329         int i;
14330         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14331           boards[0][VIRGIN][i] = 0;
14332           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14333           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14334         }
14335       }
14336     }
14337     SendToProgram("force\n", &first);
14338     if (blackPlaysFirst) {
14339         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14340         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14341         currentMove = forwardMostMove = backwardMostMove = 1;
14342         CopyBoard(boards[1], boards[0]);
14343     } else {
14344         currentMove = forwardMostMove = backwardMostMove = 0;
14345     }
14346     SendBoard(&first, forwardMostMove);
14347     if (appData.debugMode) {
14348         fprintf(debugFP, "EditPosDone\n");
14349     }
14350     DisplayTitle("");
14351     DisplayMessage("", "");
14352     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14353     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14354     gameMode = EditGame;
14355     ModeHighlight();
14356     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14357     ClearHighlights(); /* [AS] */
14358 }
14359
14360 /* Pause for `ms' milliseconds */
14361 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14362 void
14363 TimeDelay (long ms)
14364 {
14365     TimeMark m1, m2;
14366
14367     GetTimeMark(&m1);
14368     do {
14369         GetTimeMark(&m2);
14370     } while (SubtractTimeMarks(&m2, &m1) < ms);
14371 }
14372
14373 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14374 void
14375 SendMultiLineToICS (char *buf)
14376 {
14377     char temp[MSG_SIZ+1], *p;
14378     int len;
14379
14380     len = strlen(buf);
14381     if (len > MSG_SIZ)
14382       len = MSG_SIZ;
14383
14384     strncpy(temp, buf, len);
14385     temp[len] = 0;
14386
14387     p = temp;
14388     while (*p) {
14389         if (*p == '\n' || *p == '\r')
14390           *p = ' ';
14391         ++p;
14392     }
14393
14394     strcat(temp, "\n");
14395     SendToICS(temp);
14396     SendToPlayer(temp, strlen(temp));
14397 }
14398
14399 void
14400 SetWhiteToPlayEvent ()
14401 {
14402     if (gameMode == EditPosition) {
14403         blackPlaysFirst = FALSE;
14404         DisplayBothClocks();    /* works because currentMove is 0 */
14405     } else if (gameMode == IcsExamining) {
14406         SendToICS(ics_prefix);
14407         SendToICS("tomove white\n");
14408     }
14409 }
14410
14411 void
14412 SetBlackToPlayEvent ()
14413 {
14414     if (gameMode == EditPosition) {
14415         blackPlaysFirst = TRUE;
14416         currentMove = 1;        /* kludge */
14417         DisplayBothClocks();
14418         currentMove = 0;
14419     } else if (gameMode == IcsExamining) {
14420         SendToICS(ics_prefix);
14421         SendToICS("tomove black\n");
14422     }
14423 }
14424
14425 void
14426 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14427 {
14428     char buf[MSG_SIZ];
14429     ChessSquare piece = boards[0][y][x];
14430
14431     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14432
14433     switch (selection) {
14434       case ClearBoard:
14435         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14436             SendToICS(ics_prefix);
14437             SendToICS("bsetup clear\n");
14438         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14439             SendToICS(ics_prefix);
14440             SendToICS("clearboard\n");
14441         } else {
14442             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14443                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14444                 for (y = 0; y < BOARD_HEIGHT; y++) {
14445                     if (gameMode == IcsExamining) {
14446                         if (boards[currentMove][y][x] != EmptySquare) {
14447                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14448                                     AAA + x, ONE + y);
14449                             SendToICS(buf);
14450                         }
14451                     } else {
14452                         boards[0][y][x] = p;
14453                     }
14454                 }
14455             }
14456         }
14457         if (gameMode == EditPosition) {
14458             DrawPosition(FALSE, boards[0]);
14459         }
14460         break;
14461
14462       case WhitePlay:
14463         SetWhiteToPlayEvent();
14464         break;
14465
14466       case BlackPlay:
14467         SetBlackToPlayEvent();
14468         break;
14469
14470       case EmptySquare:
14471         if (gameMode == IcsExamining) {
14472             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14473             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14474             SendToICS(buf);
14475         } else {
14476             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14477                 if(x == BOARD_LEFT-2) {
14478                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14479                     boards[0][y][1] = 0;
14480                 } else
14481                 if(x == BOARD_RGHT+1) {
14482                     if(y >= gameInfo.holdingsSize) break;
14483                     boards[0][y][BOARD_WIDTH-2] = 0;
14484                 } else break;
14485             }
14486             boards[0][y][x] = EmptySquare;
14487             DrawPosition(FALSE, boards[0]);
14488         }
14489         break;
14490
14491       case PromotePiece:
14492         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14493            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14494             selection = (ChessSquare) (PROMOTED piece);
14495         } else if(piece == EmptySquare) selection = WhiteSilver;
14496         else selection = (ChessSquare)((int)piece - 1);
14497         goto defaultlabel;
14498
14499       case DemotePiece:
14500         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14501            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14502             selection = (ChessSquare) (DEMOTED piece);
14503         } else if(piece == EmptySquare) selection = BlackSilver;
14504         else selection = (ChessSquare)((int)piece + 1);
14505         goto defaultlabel;
14506
14507       case WhiteQueen:
14508       case BlackQueen:
14509         if(gameInfo.variant == VariantShatranj ||
14510            gameInfo.variant == VariantXiangqi  ||
14511            gameInfo.variant == VariantCourier  ||
14512            gameInfo.variant == VariantMakruk     )
14513             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14514         goto defaultlabel;
14515
14516       case WhiteKing:
14517       case BlackKing:
14518         if(gameInfo.variant == VariantXiangqi)
14519             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14520         if(gameInfo.variant == VariantKnightmate)
14521             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14522       default:
14523         defaultlabel:
14524         if (gameMode == IcsExamining) {
14525             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14526             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14527                      PieceToChar(selection), AAA + x, ONE + y);
14528             SendToICS(buf);
14529         } else {
14530             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14531                 int n;
14532                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14533                     n = PieceToNumber(selection - BlackPawn);
14534                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14535                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14536                     boards[0][BOARD_HEIGHT-1-n][1]++;
14537                 } else
14538                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14539                     n = PieceToNumber(selection);
14540                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14541                     boards[0][n][BOARD_WIDTH-1] = selection;
14542                     boards[0][n][BOARD_WIDTH-2]++;
14543                 }
14544             } else
14545             boards[0][y][x] = selection;
14546             DrawPosition(TRUE, boards[0]);
14547             ClearHighlights();
14548             fromX = fromY = -1;
14549         }
14550         break;
14551     }
14552 }
14553
14554
14555 void
14556 DropMenuEvent (ChessSquare selection, int x, int y)
14557 {
14558     ChessMove moveType;
14559
14560     switch (gameMode) {
14561       case IcsPlayingWhite:
14562       case MachinePlaysBlack:
14563         if (!WhiteOnMove(currentMove)) {
14564             DisplayMoveError(_("It is Black's turn"));
14565             return;
14566         }
14567         moveType = WhiteDrop;
14568         break;
14569       case IcsPlayingBlack:
14570       case MachinePlaysWhite:
14571         if (WhiteOnMove(currentMove)) {
14572             DisplayMoveError(_("It is White's turn"));
14573             return;
14574         }
14575         moveType = BlackDrop;
14576         break;
14577       case EditGame:
14578         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14579         break;
14580       default:
14581         return;
14582     }
14583
14584     if (moveType == BlackDrop && selection < BlackPawn) {
14585       selection = (ChessSquare) ((int) selection
14586                                  + (int) BlackPawn - (int) WhitePawn);
14587     }
14588     if (boards[currentMove][y][x] != EmptySquare) {
14589         DisplayMoveError(_("That square is occupied"));
14590         return;
14591     }
14592
14593     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14594 }
14595
14596 void
14597 AcceptEvent ()
14598 {
14599     /* Accept a pending offer of any kind from opponent */
14600
14601     if (appData.icsActive) {
14602         SendToICS(ics_prefix);
14603         SendToICS("accept\n");
14604     } else if (cmailMsgLoaded) {
14605         if (currentMove == cmailOldMove &&
14606             commentList[cmailOldMove] != NULL &&
14607             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14608                    "Black offers a draw" : "White offers a draw")) {
14609             TruncateGame();
14610             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14611             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14612         } else {
14613             DisplayError(_("There is no pending offer on this move"), 0);
14614             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14615         }
14616     } else {
14617         /* Not used for offers from chess program */
14618     }
14619 }
14620
14621 void
14622 DeclineEvent ()
14623 {
14624     /* Decline a pending offer of any kind from opponent */
14625
14626     if (appData.icsActive) {
14627         SendToICS(ics_prefix);
14628         SendToICS("decline\n");
14629     } else if (cmailMsgLoaded) {
14630         if (currentMove == cmailOldMove &&
14631             commentList[cmailOldMove] != NULL &&
14632             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14633                    "Black offers a draw" : "White offers a draw")) {
14634 #ifdef NOTDEF
14635             AppendComment(cmailOldMove, "Draw declined", TRUE);
14636             DisplayComment(cmailOldMove - 1, "Draw declined");
14637 #endif /*NOTDEF*/
14638         } else {
14639             DisplayError(_("There is no pending offer on this move"), 0);
14640         }
14641     } else {
14642         /* Not used for offers from chess program */
14643     }
14644 }
14645
14646 void
14647 RematchEvent ()
14648 {
14649     /* Issue ICS rematch command */
14650     if (appData.icsActive) {
14651         SendToICS(ics_prefix);
14652         SendToICS("rematch\n");
14653     }
14654 }
14655
14656 void
14657 CallFlagEvent ()
14658 {
14659     /* Call your opponent's flag (claim a win on time) */
14660     if (appData.icsActive) {
14661         SendToICS(ics_prefix);
14662         SendToICS("flag\n");
14663     } else {
14664         switch (gameMode) {
14665           default:
14666             return;
14667           case MachinePlaysWhite:
14668             if (whiteFlag) {
14669                 if (blackFlag)
14670                   GameEnds(GameIsDrawn, "Both players ran out of time",
14671                            GE_PLAYER);
14672                 else
14673                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14674             } else {
14675                 DisplayError(_("Your opponent is not out of time"), 0);
14676             }
14677             break;
14678           case MachinePlaysBlack:
14679             if (blackFlag) {
14680                 if (whiteFlag)
14681                   GameEnds(GameIsDrawn, "Both players ran out of time",
14682                            GE_PLAYER);
14683                 else
14684                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14685             } else {
14686                 DisplayError(_("Your opponent is not out of time"), 0);
14687             }
14688             break;
14689         }
14690     }
14691 }
14692
14693 void
14694 ClockClick (int which)
14695 {       // [HGM] code moved to back-end from winboard.c
14696         if(which) { // black clock
14697           if (gameMode == EditPosition || gameMode == IcsExamining) {
14698             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14699             SetBlackToPlayEvent();
14700           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14701           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14702           } else if (shiftKey) {
14703             AdjustClock(which, -1);
14704           } else if (gameMode == IcsPlayingWhite ||
14705                      gameMode == MachinePlaysBlack) {
14706             CallFlagEvent();
14707           }
14708         } else { // white clock
14709           if (gameMode == EditPosition || gameMode == IcsExamining) {
14710             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14711             SetWhiteToPlayEvent();
14712           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14713           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14714           } else if (shiftKey) {
14715             AdjustClock(which, -1);
14716           } else if (gameMode == IcsPlayingBlack ||
14717                    gameMode == MachinePlaysWhite) {
14718             CallFlagEvent();
14719           }
14720         }
14721 }
14722
14723 void
14724 DrawEvent ()
14725 {
14726     /* Offer draw or accept pending draw offer from opponent */
14727
14728     if (appData.icsActive) {
14729         /* Note: tournament rules require draw offers to be
14730            made after you make your move but before you punch
14731            your clock.  Currently ICS doesn't let you do that;
14732            instead, you immediately punch your clock after making
14733            a move, but you can offer a draw at any time. */
14734
14735         SendToICS(ics_prefix);
14736         SendToICS("draw\n");
14737         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14738     } else if (cmailMsgLoaded) {
14739         if (currentMove == cmailOldMove &&
14740             commentList[cmailOldMove] != NULL &&
14741             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14742                    "Black offers a draw" : "White offers a draw")) {
14743             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14744             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14745         } else if (currentMove == cmailOldMove + 1) {
14746             char *offer = WhiteOnMove(cmailOldMove) ?
14747               "White offers a draw" : "Black offers a draw";
14748             AppendComment(currentMove, offer, TRUE);
14749             DisplayComment(currentMove - 1, offer);
14750             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14751         } else {
14752             DisplayError(_("You must make your move before offering a draw"), 0);
14753             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14754         }
14755     } else if (first.offeredDraw) {
14756         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14757     } else {
14758         if (first.sendDrawOffers) {
14759             SendToProgram("draw\n", &first);
14760             userOfferedDraw = TRUE;
14761         }
14762     }
14763 }
14764
14765 void
14766 AdjournEvent ()
14767 {
14768     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14769
14770     if (appData.icsActive) {
14771         SendToICS(ics_prefix);
14772         SendToICS("adjourn\n");
14773     } else {
14774         /* Currently GNU Chess doesn't offer or accept Adjourns */
14775     }
14776 }
14777
14778
14779 void
14780 AbortEvent ()
14781 {
14782     /* Offer Abort or accept pending Abort offer from opponent */
14783
14784     if (appData.icsActive) {
14785         SendToICS(ics_prefix);
14786         SendToICS("abort\n");
14787     } else {
14788         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14789     }
14790 }
14791
14792 void
14793 ResignEvent ()
14794 {
14795     /* Resign.  You can do this even if it's not your turn. */
14796
14797     if (appData.icsActive) {
14798         SendToICS(ics_prefix);
14799         SendToICS("resign\n");
14800     } else {
14801         switch (gameMode) {
14802           case MachinePlaysWhite:
14803             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14804             break;
14805           case MachinePlaysBlack:
14806             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14807             break;
14808           case EditGame:
14809             if (cmailMsgLoaded) {
14810                 TruncateGame();
14811                 if (WhiteOnMove(cmailOldMove)) {
14812                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14813                 } else {
14814                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14815                 }
14816                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14817             }
14818             break;
14819           default:
14820             break;
14821         }
14822     }
14823 }
14824
14825
14826 void
14827 StopObservingEvent ()
14828 {
14829     /* Stop observing current games */
14830     SendToICS(ics_prefix);
14831     SendToICS("unobserve\n");
14832 }
14833
14834 void
14835 StopExaminingEvent ()
14836 {
14837     /* Stop observing current game */
14838     SendToICS(ics_prefix);
14839     SendToICS("unexamine\n");
14840 }
14841
14842 void
14843 ForwardInner (int target)
14844 {
14845     int limit; int oldSeekGraphUp = seekGraphUp;
14846
14847     if (appData.debugMode)
14848         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14849                 target, currentMove, forwardMostMove);
14850
14851     if (gameMode == EditPosition)
14852       return;
14853
14854     seekGraphUp = FALSE;
14855     MarkTargetSquares(1);
14856
14857     if (gameMode == PlayFromGameFile && !pausing)
14858       PauseEvent();
14859
14860     if (gameMode == IcsExamining && pausing)
14861       limit = pauseExamForwardMostMove;
14862     else
14863       limit = forwardMostMove;
14864
14865     if (target > limit) target = limit;
14866
14867     if (target > 0 && moveList[target - 1][0]) {
14868         int fromX, fromY, toX, toY;
14869         toX = moveList[target - 1][2] - AAA;
14870         toY = moveList[target - 1][3] - ONE;
14871         if (moveList[target - 1][1] == '@') {
14872             if (appData.highlightLastMove) {
14873                 SetHighlights(-1, -1, toX, toY);
14874             }
14875         } else {
14876             fromX = moveList[target - 1][0] - AAA;
14877             fromY = moveList[target - 1][1] - ONE;
14878             if (target == currentMove + 1) {
14879                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14880             }
14881             if (appData.highlightLastMove) {
14882                 SetHighlights(fromX, fromY, toX, toY);
14883             }
14884         }
14885     }
14886     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14887         gameMode == Training || gameMode == PlayFromGameFile ||
14888         gameMode == AnalyzeFile) {
14889         while (currentMove < target) {
14890             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14891             SendMoveToProgram(currentMove++, &first);
14892         }
14893     } else {
14894         currentMove = target;
14895     }
14896
14897     if (gameMode == EditGame || gameMode == EndOfGame) {
14898         whiteTimeRemaining = timeRemaining[0][currentMove];
14899         blackTimeRemaining = timeRemaining[1][currentMove];
14900     }
14901     DisplayBothClocks();
14902     DisplayMove(currentMove - 1);
14903     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14904     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14905     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14906         DisplayComment(currentMove - 1, commentList[currentMove]);
14907     }
14908     ClearMap(); // [HGM] exclude: invalidate map
14909 }
14910
14911
14912 void
14913 ForwardEvent ()
14914 {
14915     if (gameMode == IcsExamining && !pausing) {
14916         SendToICS(ics_prefix);
14917         SendToICS("forward\n");
14918     } else {
14919         ForwardInner(currentMove + 1);
14920     }
14921 }
14922
14923 void
14924 ToEndEvent ()
14925 {
14926     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14927         /* to optimze, we temporarily turn off analysis mode while we feed
14928          * the remaining moves to the engine. Otherwise we get analysis output
14929          * after each move.
14930          */
14931         if (first.analysisSupport) {
14932           SendToProgram("exit\nforce\n", &first);
14933           first.analyzing = FALSE;
14934         }
14935     }
14936
14937     if (gameMode == IcsExamining && !pausing) {
14938         SendToICS(ics_prefix);
14939         SendToICS("forward 999999\n");
14940     } else {
14941         ForwardInner(forwardMostMove);
14942     }
14943
14944     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14945         /* we have fed all the moves, so reactivate analysis mode */
14946         SendToProgram("analyze\n", &first);
14947         first.analyzing = TRUE;
14948         /*first.maybeThinking = TRUE;*/
14949         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14950     }
14951 }
14952
14953 void
14954 BackwardInner (int target)
14955 {
14956     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14957
14958     if (appData.debugMode)
14959         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14960                 target, currentMove, forwardMostMove);
14961
14962     if (gameMode == EditPosition) return;
14963     seekGraphUp = FALSE;
14964     MarkTargetSquares(1);
14965     if (currentMove <= backwardMostMove) {
14966         ClearHighlights();
14967         DrawPosition(full_redraw, boards[currentMove]);
14968         return;
14969     }
14970     if (gameMode == PlayFromGameFile && !pausing)
14971       PauseEvent();
14972
14973     if (moveList[target][0]) {
14974         int fromX, fromY, toX, toY;
14975         toX = moveList[target][2] - AAA;
14976         toY = moveList[target][3] - ONE;
14977         if (moveList[target][1] == '@') {
14978             if (appData.highlightLastMove) {
14979                 SetHighlights(-1, -1, toX, toY);
14980             }
14981         } else {
14982             fromX = moveList[target][0] - AAA;
14983             fromY = moveList[target][1] - ONE;
14984             if (target == currentMove - 1) {
14985                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14986             }
14987             if (appData.highlightLastMove) {
14988                 SetHighlights(fromX, fromY, toX, toY);
14989             }
14990         }
14991     }
14992     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14993         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14994         while (currentMove > target) {
14995             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14996                 // null move cannot be undone. Reload program with move history before it.
14997                 int i;
14998                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14999                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15000                 }
15001                 SendBoard(&first, i);
15002               if(second.analyzing) SendBoard(&second, i);
15003                 for(currentMove=i; currentMove<target; currentMove++) {
15004                     SendMoveToProgram(currentMove, &first);
15005                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15006                 }
15007                 break;
15008             }
15009             SendToBoth("undo\n");
15010             currentMove--;
15011         }
15012     } else {
15013         currentMove = target;
15014     }
15015
15016     if (gameMode == EditGame || gameMode == EndOfGame) {
15017         whiteTimeRemaining = timeRemaining[0][currentMove];
15018         blackTimeRemaining = timeRemaining[1][currentMove];
15019     }
15020     DisplayBothClocks();
15021     DisplayMove(currentMove - 1);
15022     DrawPosition(full_redraw, boards[currentMove]);
15023     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15024     // [HGM] PV info: routine tests if comment empty
15025     DisplayComment(currentMove - 1, commentList[currentMove]);
15026     ClearMap(); // [HGM] exclude: invalidate map
15027 }
15028
15029 void
15030 BackwardEvent ()
15031 {
15032     if (gameMode == IcsExamining && !pausing) {
15033         SendToICS(ics_prefix);
15034         SendToICS("backward\n");
15035     } else {
15036         BackwardInner(currentMove - 1);
15037     }
15038 }
15039
15040 void
15041 ToStartEvent ()
15042 {
15043     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15044         /* to optimize, we temporarily turn off analysis mode while we undo
15045          * all the moves. Otherwise we get analysis output after each undo.
15046          */
15047         if (first.analysisSupport) {
15048           SendToProgram("exit\nforce\n", &first);
15049           first.analyzing = FALSE;
15050         }
15051     }
15052
15053     if (gameMode == IcsExamining && !pausing) {
15054         SendToICS(ics_prefix);
15055         SendToICS("backward 999999\n");
15056     } else {
15057         BackwardInner(backwardMostMove);
15058     }
15059
15060     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15061         /* we have fed all the moves, so reactivate analysis mode */
15062         SendToProgram("analyze\n", &first);
15063         first.analyzing = TRUE;
15064         /*first.maybeThinking = TRUE;*/
15065         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15066     }
15067 }
15068
15069 void
15070 ToNrEvent (int to)
15071 {
15072   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15073   if (to >= forwardMostMove) to = forwardMostMove;
15074   if (to <= backwardMostMove) to = backwardMostMove;
15075   if (to < currentMove) {
15076     BackwardInner(to);
15077   } else {
15078     ForwardInner(to);
15079   }
15080 }
15081
15082 void
15083 RevertEvent (Boolean annotate)
15084 {
15085     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15086         return;
15087     }
15088     if (gameMode != IcsExamining) {
15089         DisplayError(_("You are not examining a game"), 0);
15090         return;
15091     }
15092     if (pausing) {
15093         DisplayError(_("You can't revert while pausing"), 0);
15094         return;
15095     }
15096     SendToICS(ics_prefix);
15097     SendToICS("revert\n");
15098 }
15099
15100 void
15101 RetractMoveEvent ()
15102 {
15103     switch (gameMode) {
15104       case MachinePlaysWhite:
15105       case MachinePlaysBlack:
15106         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15107             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
15108             return;
15109         }
15110         if (forwardMostMove < 2) return;
15111         currentMove = forwardMostMove = forwardMostMove - 2;
15112         whiteTimeRemaining = timeRemaining[0][currentMove];
15113         blackTimeRemaining = timeRemaining[1][currentMove];
15114         DisplayBothClocks();
15115         DisplayMove(currentMove - 1);
15116         ClearHighlights();/*!! could figure this out*/
15117         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15118         SendToProgram("remove\n", &first);
15119         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15120         break;
15121
15122       case BeginningOfGame:
15123       default:
15124         break;
15125
15126       case IcsPlayingWhite:
15127       case IcsPlayingBlack:
15128         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15129             SendToICS(ics_prefix);
15130             SendToICS("takeback 2\n");
15131         } else {
15132             SendToICS(ics_prefix);
15133             SendToICS("takeback 1\n");
15134         }
15135         break;
15136     }
15137 }
15138
15139 void
15140 MoveNowEvent ()
15141 {
15142     ChessProgramState *cps;
15143
15144     switch (gameMode) {
15145       case MachinePlaysWhite:
15146         if (!WhiteOnMove(forwardMostMove)) {
15147             DisplayError(_("It is your turn"), 0);
15148             return;
15149         }
15150         cps = &first;
15151         break;
15152       case MachinePlaysBlack:
15153         if (WhiteOnMove(forwardMostMove)) {
15154             DisplayError(_("It is your turn"), 0);
15155             return;
15156         }
15157         cps = &first;
15158         break;
15159       case TwoMachinesPlay:
15160         if (WhiteOnMove(forwardMostMove) ==
15161             (first.twoMachinesColor[0] == 'w')) {
15162             cps = &first;
15163         } else {
15164             cps = &second;
15165         }
15166         break;
15167       case BeginningOfGame:
15168       default:
15169         return;
15170     }
15171     SendToProgram("?\n", cps);
15172 }
15173
15174 void
15175 TruncateGameEvent ()
15176 {
15177     EditGameEvent();
15178     if (gameMode != EditGame) return;
15179     TruncateGame();
15180 }
15181
15182 void
15183 TruncateGame ()
15184 {
15185     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15186     if (forwardMostMove > currentMove) {
15187         if (gameInfo.resultDetails != NULL) {
15188             free(gameInfo.resultDetails);
15189             gameInfo.resultDetails = NULL;
15190             gameInfo.result = GameUnfinished;
15191         }
15192         forwardMostMove = currentMove;
15193         HistorySet(parseList, backwardMostMove, forwardMostMove,
15194                    currentMove-1);
15195     }
15196 }
15197
15198 void
15199 HintEvent ()
15200 {
15201     if (appData.noChessProgram) return;
15202     switch (gameMode) {
15203       case MachinePlaysWhite:
15204         if (WhiteOnMove(forwardMostMove)) {
15205             DisplayError(_("Wait until your turn"), 0);
15206             return;
15207         }
15208         break;
15209       case BeginningOfGame:
15210       case MachinePlaysBlack:
15211         if (!WhiteOnMove(forwardMostMove)) {
15212             DisplayError(_("Wait until your turn"), 0);
15213             return;
15214         }
15215         break;
15216       default:
15217         DisplayError(_("No hint available"), 0);
15218         return;
15219     }
15220     SendToProgram("hint\n", &first);
15221     hintRequested = TRUE;
15222 }
15223
15224 void
15225 CreateBookEvent ()
15226 {
15227     ListGame * lg = (ListGame *) gameList.head;
15228     FILE *f, *g;
15229     int nItem;
15230     static int secondTime = FALSE;
15231
15232     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15233         DisplayError(_("Game list not loaded or empty"), 0);
15234         return;
15235     }
15236
15237     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15238         fclose(g);
15239         secondTime++;
15240         DisplayNote(_("Book file exists! Try again for overwrite."));
15241         return;
15242     }
15243
15244     creatingBook = TRUE;
15245     secondTime = FALSE;
15246
15247     /* Get list size */
15248     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15249         LoadGame(f, nItem, "", TRUE);
15250         AddGameToBook(TRUE);
15251         lg = (ListGame *) lg->node.succ;
15252     }
15253
15254     creatingBook = FALSE;
15255     FlushBook();
15256 }
15257
15258 void
15259 BookEvent ()
15260 {
15261     if (appData.noChessProgram) return;
15262     switch (gameMode) {
15263       case MachinePlaysWhite:
15264         if (WhiteOnMove(forwardMostMove)) {
15265             DisplayError(_("Wait until your turn"), 0);
15266             return;
15267         }
15268         break;
15269       case BeginningOfGame:
15270       case MachinePlaysBlack:
15271         if (!WhiteOnMove(forwardMostMove)) {
15272             DisplayError(_("Wait until your turn"), 0);
15273             return;
15274         }
15275         break;
15276       case EditPosition:
15277         EditPositionDone(TRUE);
15278         break;
15279       case TwoMachinesPlay:
15280         return;
15281       default:
15282         break;
15283     }
15284     SendToProgram("bk\n", &first);
15285     bookOutput[0] = NULLCHAR;
15286     bookRequested = TRUE;
15287 }
15288
15289 void
15290 AboutGameEvent ()
15291 {
15292     char *tags = PGNTags(&gameInfo);
15293     TagsPopUp(tags, CmailMsg());
15294     free(tags);
15295 }
15296
15297 /* end button procedures */
15298
15299 void
15300 PrintPosition (FILE *fp, int move)
15301 {
15302     int i, j;
15303
15304     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15305         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15306             char c = PieceToChar(boards[move][i][j]);
15307             fputc(c == 'x' ? '.' : c, fp);
15308             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15309         }
15310     }
15311     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15312       fprintf(fp, "white to play\n");
15313     else
15314       fprintf(fp, "black to play\n");
15315 }
15316
15317 void
15318 PrintOpponents (FILE *fp)
15319 {
15320     if (gameInfo.white != NULL) {
15321         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15322     } else {
15323         fprintf(fp, "\n");
15324     }
15325 }
15326
15327 /* Find last component of program's own name, using some heuristics */
15328 void
15329 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15330 {
15331     char *p, *q, c;
15332     int local = (strcmp(host, "localhost") == 0);
15333     while (!local && (p = strchr(prog, ';')) != NULL) {
15334         p++;
15335         while (*p == ' ') p++;
15336         prog = p;
15337     }
15338     if (*prog == '"' || *prog == '\'') {
15339         q = strchr(prog + 1, *prog);
15340     } else {
15341         q = strchr(prog, ' ');
15342     }
15343     if (q == NULL) q = prog + strlen(prog);
15344     p = q;
15345     while (p >= prog && *p != '/' && *p != '\\') p--;
15346     p++;
15347     if(p == prog && *p == '"') p++;
15348     c = *q; *q = 0;
15349     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15350     memcpy(buf, p, q - p);
15351     buf[q - p] = NULLCHAR;
15352     if (!local) {
15353         strcat(buf, "@");
15354         strcat(buf, host);
15355     }
15356 }
15357
15358 char *
15359 TimeControlTagValue ()
15360 {
15361     char buf[MSG_SIZ];
15362     if (!appData.clockMode) {
15363       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15364     } else if (movesPerSession > 0) {
15365       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15366     } else if (timeIncrement == 0) {
15367       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15368     } else {
15369       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15370     }
15371     return StrSave(buf);
15372 }
15373
15374 void
15375 SetGameInfo ()
15376 {
15377     /* This routine is used only for certain modes */
15378     VariantClass v = gameInfo.variant;
15379     ChessMove r = GameUnfinished;
15380     char *p = NULL;
15381
15382     if(keepInfo) return;
15383
15384     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15385         r = gameInfo.result;
15386         p = gameInfo.resultDetails;
15387         gameInfo.resultDetails = NULL;
15388     }
15389     ClearGameInfo(&gameInfo);
15390     gameInfo.variant = v;
15391
15392     switch (gameMode) {
15393       case MachinePlaysWhite:
15394         gameInfo.event = StrSave( appData.pgnEventHeader );
15395         gameInfo.site = StrSave(HostName());
15396         gameInfo.date = PGNDate();
15397         gameInfo.round = StrSave("-");
15398         gameInfo.white = StrSave(first.tidy);
15399         gameInfo.black = StrSave(UserName());
15400         gameInfo.timeControl = TimeControlTagValue();
15401         break;
15402
15403       case MachinePlaysBlack:
15404         gameInfo.event = StrSave( appData.pgnEventHeader );
15405         gameInfo.site = StrSave(HostName());
15406         gameInfo.date = PGNDate();
15407         gameInfo.round = StrSave("-");
15408         gameInfo.white = StrSave(UserName());
15409         gameInfo.black = StrSave(first.tidy);
15410         gameInfo.timeControl = TimeControlTagValue();
15411         break;
15412
15413       case TwoMachinesPlay:
15414         gameInfo.event = StrSave( appData.pgnEventHeader );
15415         gameInfo.site = StrSave(HostName());
15416         gameInfo.date = PGNDate();
15417         if (roundNr > 0) {
15418             char buf[MSG_SIZ];
15419             snprintf(buf, MSG_SIZ, "%d", roundNr);
15420             gameInfo.round = StrSave(buf);
15421         } else {
15422             gameInfo.round = StrSave("-");
15423         }
15424         if (first.twoMachinesColor[0] == 'w') {
15425             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15426             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15427         } else {
15428             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15429             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15430         }
15431         gameInfo.timeControl = TimeControlTagValue();
15432         break;
15433
15434       case EditGame:
15435         gameInfo.event = StrSave("Edited game");
15436         gameInfo.site = StrSave(HostName());
15437         gameInfo.date = PGNDate();
15438         gameInfo.round = StrSave("-");
15439         gameInfo.white = StrSave("-");
15440         gameInfo.black = StrSave("-");
15441         gameInfo.result = r;
15442         gameInfo.resultDetails = p;
15443         break;
15444
15445       case EditPosition:
15446         gameInfo.event = StrSave("Edited position");
15447         gameInfo.site = StrSave(HostName());
15448         gameInfo.date = PGNDate();
15449         gameInfo.round = StrSave("-");
15450         gameInfo.white = StrSave("-");
15451         gameInfo.black = StrSave("-");
15452         break;
15453
15454       case IcsPlayingWhite:
15455       case IcsPlayingBlack:
15456       case IcsObserving:
15457       case IcsExamining:
15458         break;
15459
15460       case PlayFromGameFile:
15461         gameInfo.event = StrSave("Game from non-PGN file");
15462         gameInfo.site = StrSave(HostName());
15463         gameInfo.date = PGNDate();
15464         gameInfo.round = StrSave("-");
15465         gameInfo.white = StrSave("?");
15466         gameInfo.black = StrSave("?");
15467         break;
15468
15469       default:
15470         break;
15471     }
15472 }
15473
15474 void
15475 ReplaceComment (int index, char *text)
15476 {
15477     int len;
15478     char *p;
15479     float score;
15480
15481     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15482        pvInfoList[index-1].depth == len &&
15483        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15484        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15485     while (*text == '\n') text++;
15486     len = strlen(text);
15487     while (len > 0 && text[len - 1] == '\n') len--;
15488
15489     if (commentList[index] != NULL)
15490       free(commentList[index]);
15491
15492     if (len == 0) {
15493         commentList[index] = NULL;
15494         return;
15495     }
15496   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15497       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15498       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15499     commentList[index] = (char *) malloc(len + 2);
15500     strncpy(commentList[index], text, len);
15501     commentList[index][len] = '\n';
15502     commentList[index][len + 1] = NULLCHAR;
15503   } else {
15504     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15505     char *p;
15506     commentList[index] = (char *) malloc(len + 7);
15507     safeStrCpy(commentList[index], "{\n", 3);
15508     safeStrCpy(commentList[index]+2, text, len+1);
15509     commentList[index][len+2] = NULLCHAR;
15510     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15511     strcat(commentList[index], "\n}\n");
15512   }
15513 }
15514
15515 void
15516 CrushCRs (char *text)
15517 {
15518   char *p = text;
15519   char *q = text;
15520   char ch;
15521
15522   do {
15523     ch = *p++;
15524     if (ch == '\r') continue;
15525     *q++ = ch;
15526   } while (ch != '\0');
15527 }
15528
15529 void
15530 AppendComment (int index, char *text, Boolean addBraces)
15531 /* addBraces  tells if we should add {} */
15532 {
15533     int oldlen, len;
15534     char *old;
15535
15536 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15537     if(addBraces == 3) addBraces = 0; else // force appending literally
15538     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15539
15540     CrushCRs(text);
15541     while (*text == '\n') text++;
15542     len = strlen(text);
15543     while (len > 0 && text[len - 1] == '\n') len--;
15544     text[len] = NULLCHAR;
15545
15546     if (len == 0) return;
15547
15548     if (commentList[index] != NULL) {
15549       Boolean addClosingBrace = addBraces;
15550         old = commentList[index];
15551         oldlen = strlen(old);
15552         while(commentList[index][oldlen-1] ==  '\n')
15553           commentList[index][--oldlen] = NULLCHAR;
15554         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15555         safeStrCpy(commentList[index], old, oldlen + len + 6);
15556         free(old);
15557         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15558         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15559           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15560           while (*text == '\n') { text++; len--; }
15561           commentList[index][--oldlen] = NULLCHAR;
15562       }
15563         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15564         else          strcat(commentList[index], "\n");
15565         strcat(commentList[index], text);
15566         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15567         else          strcat(commentList[index], "\n");
15568     } else {
15569         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15570         if(addBraces)
15571           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15572         else commentList[index][0] = NULLCHAR;
15573         strcat(commentList[index], text);
15574         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15575         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15576     }
15577 }
15578
15579 static char *
15580 FindStr (char * text, char * sub_text)
15581 {
15582     char * result = strstr( text, sub_text );
15583
15584     if( result != NULL ) {
15585         result += strlen( sub_text );
15586     }
15587
15588     return result;
15589 }
15590
15591 /* [AS] Try to extract PV info from PGN comment */
15592 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15593 char *
15594 GetInfoFromComment (int index, char * text)
15595 {
15596     char * sep = text, *p;
15597
15598     if( text != NULL && index > 0 ) {
15599         int score = 0;
15600         int depth = 0;
15601         int time = -1, sec = 0, deci;
15602         char * s_eval = FindStr( text, "[%eval " );
15603         char * s_emt = FindStr( text, "[%emt " );
15604 #if 0
15605         if( s_eval != NULL || s_emt != NULL ) {
15606 #else
15607         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15608 #endif
15609             /* New style */
15610             char delim;
15611
15612             if( s_eval != NULL ) {
15613                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15614                     return text;
15615                 }
15616
15617                 if( delim != ']' ) {
15618                     return text;
15619                 }
15620             }
15621
15622             if( s_emt != NULL ) {
15623             }
15624                 return text;
15625         }
15626         else {
15627             /* We expect something like: [+|-]nnn.nn/dd */
15628             int score_lo = 0;
15629
15630             if(*text != '{') return text; // [HGM] braces: must be normal comment
15631
15632             sep = strchr( text, '/' );
15633             if( sep == NULL || sep < (text+4) ) {
15634                 return text;
15635             }
15636
15637             p = text;
15638             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15639             if(p[1] == '(') { // comment starts with PV
15640                p = strchr(p, ')'); // locate end of PV
15641                if(p == NULL || sep < p+5) return text;
15642                // at this point we have something like "{(.*) +0.23/6 ..."
15643                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15644                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15645                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15646             }
15647             time = -1; sec = -1; deci = -1;
15648             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15649                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15650                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15651                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15652                 return text;
15653             }
15654
15655             if( score_lo < 0 || score_lo >= 100 ) {
15656                 return text;
15657             }
15658
15659             if(sec >= 0) time = 600*time + 10*sec; else
15660             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15661
15662             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
15663
15664             /* [HGM] PV time: now locate end of PV info */
15665             while( *++sep >= '0' && *sep <= '9'); // strip depth
15666             if(time >= 0)
15667             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15668             if(sec >= 0)
15669             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15670             if(deci >= 0)
15671             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15672             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15673         }
15674
15675         if( depth <= 0 ) {
15676             return text;
15677         }
15678
15679         if( time < 0 ) {
15680             time = -1;
15681         }
15682
15683         pvInfoList[index-1].depth = depth;
15684         pvInfoList[index-1].score = score;
15685         pvInfoList[index-1].time  = 10*time; // centi-sec
15686         if(*sep == '}') *sep = 0; else *--sep = '{';
15687         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15688     }
15689     return sep;
15690 }
15691
15692 void
15693 SendToProgram (char *message, ChessProgramState *cps)
15694 {
15695     int count, outCount, error;
15696     char buf[MSG_SIZ];
15697
15698     if (cps->pr == NoProc) return;
15699     Attention(cps);
15700
15701     if (appData.debugMode) {
15702         TimeMark now;
15703         GetTimeMark(&now);
15704         fprintf(debugFP, "%ld >%-6s: %s",
15705                 SubtractTimeMarks(&now, &programStartTime),
15706                 cps->which, message);
15707         if(serverFP)
15708             fprintf(serverFP, "%ld >%-6s: %s",
15709                 SubtractTimeMarks(&now, &programStartTime),
15710                 cps->which, message), fflush(serverFP);
15711     }
15712
15713     count = strlen(message);
15714     outCount = OutputToProcess(cps->pr, message, count, &error);
15715     if (outCount < count && !exiting
15716                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15717       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15718       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15719         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15720             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15721                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15722                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15723                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15724             } else {
15725                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15726                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15727                 gameInfo.result = res;
15728             }
15729             gameInfo.resultDetails = StrSave(buf);
15730         }
15731         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15732         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15733     }
15734 }
15735
15736 void
15737 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15738 {
15739     char *end_str;
15740     char buf[MSG_SIZ];
15741     ChessProgramState *cps = (ChessProgramState *)closure;
15742
15743     if (isr != cps->isr) return; /* Killed intentionally */
15744     if (count <= 0) {
15745         if (count == 0) {
15746             RemoveInputSource(cps->isr);
15747             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15748                     _(cps->which), cps->program);
15749             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15750             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15751                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15752                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15753                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15754                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15755                 } else {
15756                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15757                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15758                     gameInfo.result = res;
15759                 }
15760                 gameInfo.resultDetails = StrSave(buf);
15761             }
15762             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15763             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15764         } else {
15765             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15766                     _(cps->which), cps->program);
15767             RemoveInputSource(cps->isr);
15768
15769             /* [AS] Program is misbehaving badly... kill it */
15770             if( count == -2 ) {
15771                 DestroyChildProcess( cps->pr, 9 );
15772                 cps->pr = NoProc;
15773             }
15774
15775             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15776         }
15777         return;
15778     }
15779
15780     if ((end_str = strchr(message, '\r')) != NULL)
15781       *end_str = NULLCHAR;
15782     if ((end_str = strchr(message, '\n')) != NULL)
15783       *end_str = NULLCHAR;
15784
15785     if (appData.debugMode) {
15786         TimeMark now; int print = 1;
15787         char *quote = ""; char c; int i;
15788
15789         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15790                 char start = message[0];
15791                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15792                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15793                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15794                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15795                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15796                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15797                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15798                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15799                    sscanf(message, "hint: %c", &c)!=1 &&
15800                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15801                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15802                     print = (appData.engineComments >= 2);
15803                 }
15804                 message[0] = start; // restore original message
15805         }
15806         if(print) {
15807                 GetTimeMark(&now);
15808                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15809                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15810                         quote,
15811                         message);
15812                 if(serverFP)
15813                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15814                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15815                         quote,
15816                         message), fflush(serverFP);
15817         }
15818     }
15819
15820     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15821     if (appData.icsEngineAnalyze) {
15822         if (strstr(message, "whisper") != NULL ||
15823              strstr(message, "kibitz") != NULL ||
15824             strstr(message, "tellics") != NULL) return;
15825     }
15826
15827     HandleMachineMove(message, cps);
15828 }
15829
15830
15831 void
15832 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15833 {
15834     char buf[MSG_SIZ];
15835     int seconds;
15836
15837     if( timeControl_2 > 0 ) {
15838         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15839             tc = timeControl_2;
15840         }
15841     }
15842     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15843     inc /= cps->timeOdds;
15844     st  /= cps->timeOdds;
15845
15846     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15847
15848     if (st > 0) {
15849       /* Set exact time per move, normally using st command */
15850       if (cps->stKludge) {
15851         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15852         seconds = st % 60;
15853         if (seconds == 0) {
15854           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15855         } else {
15856           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15857         }
15858       } else {
15859         snprintf(buf, MSG_SIZ, "st %d\n", st);
15860       }
15861     } else {
15862       /* Set conventional or incremental time control, using level command */
15863       if (seconds == 0) {
15864         /* Note old gnuchess bug -- minutes:seconds used to not work.
15865            Fixed in later versions, but still avoid :seconds
15866            when seconds is 0. */
15867         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15868       } else {
15869         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15870                  seconds, inc/1000.);
15871       }
15872     }
15873     SendToProgram(buf, cps);
15874
15875     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15876     /* Orthogonally, limit search to given depth */
15877     if (sd > 0) {
15878       if (cps->sdKludge) {
15879         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15880       } else {
15881         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15882       }
15883       SendToProgram(buf, cps);
15884     }
15885
15886     if(cps->nps >= 0) { /* [HGM] nps */
15887         if(cps->supportsNPS == FALSE)
15888           cps->nps = -1; // don't use if engine explicitly says not supported!
15889         else {
15890           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15891           SendToProgram(buf, cps);
15892         }
15893     }
15894 }
15895
15896 ChessProgramState *
15897 WhitePlayer ()
15898 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15899 {
15900     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15901        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15902         return &second;
15903     return &first;
15904 }
15905
15906 void
15907 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15908 {
15909     char message[MSG_SIZ];
15910     long time, otime;
15911
15912     /* Note: this routine must be called when the clocks are stopped
15913        or when they have *just* been set or switched; otherwise
15914        it will be off by the time since the current tick started.
15915     */
15916     if (machineWhite) {
15917         time = whiteTimeRemaining / 10;
15918         otime = blackTimeRemaining / 10;
15919     } else {
15920         time = blackTimeRemaining / 10;
15921         otime = whiteTimeRemaining / 10;
15922     }
15923     /* [HGM] translate opponent's time by time-odds factor */
15924     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15925
15926     if (time <= 0) time = 1;
15927     if (otime <= 0) otime = 1;
15928
15929     snprintf(message, MSG_SIZ, "time %ld\n", time);
15930     SendToProgram(message, cps);
15931
15932     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15933     SendToProgram(message, cps);
15934 }
15935
15936 int
15937 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15938 {
15939   char buf[MSG_SIZ];
15940   int len = strlen(name);
15941   int val;
15942
15943   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15944     (*p) += len + 1;
15945     sscanf(*p, "%d", &val);
15946     *loc = (val != 0);
15947     while (**p && **p != ' ')
15948       (*p)++;
15949     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15950     SendToProgram(buf, cps);
15951     return TRUE;
15952   }
15953   return FALSE;
15954 }
15955
15956 int
15957 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15958 {
15959   char buf[MSG_SIZ];
15960   int len = strlen(name);
15961   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15962     (*p) += len + 1;
15963     sscanf(*p, "%d", loc);
15964     while (**p && **p != ' ') (*p)++;
15965     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15966     SendToProgram(buf, cps);
15967     return TRUE;
15968   }
15969   return FALSE;
15970 }
15971
15972 int
15973 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
15974 {
15975   char buf[MSG_SIZ];
15976   int len = strlen(name);
15977   if (strncmp((*p), name, len) == 0
15978       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15979     (*p) += len + 2;
15980     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
15981     sscanf(*p, "%[^\"]", *loc);
15982     while (**p && **p != '\"') (*p)++;
15983     if (**p == '\"') (*p)++;
15984     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15985     SendToProgram(buf, cps);
15986     return TRUE;
15987   }
15988   return FALSE;
15989 }
15990
15991 int
15992 ParseOption (Option *opt, ChessProgramState *cps)
15993 // [HGM] options: process the string that defines an engine option, and determine
15994 // name, type, default value, and allowed value range
15995 {
15996         char *p, *q, buf[MSG_SIZ];
15997         int n, min = (-1)<<31, max = 1<<31, def;
15998
15999         if(p = strstr(opt->name, " -spin ")) {
16000             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16001             if(max < min) max = min; // enforce consistency
16002             if(def < min) def = min;
16003             if(def > max) def = max;
16004             opt->value = def;
16005             opt->min = min;
16006             opt->max = max;
16007             opt->type = Spin;
16008         } else if((p = strstr(opt->name, " -slider "))) {
16009             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16010             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16011             if(max < min) max = min; // enforce consistency
16012             if(def < min) def = min;
16013             if(def > max) def = max;
16014             opt->value = def;
16015             opt->min = min;
16016             opt->max = max;
16017             opt->type = Spin; // Slider;
16018         } else if((p = strstr(opt->name, " -string "))) {
16019             opt->textValue = p+9;
16020             opt->type = TextBox;
16021         } else if((p = strstr(opt->name, " -file "))) {
16022             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16023             opt->textValue = p+7;
16024             opt->type = FileName; // FileName;
16025         } else if((p = strstr(opt->name, " -path "))) {
16026             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16027             opt->textValue = p+7;
16028             opt->type = PathName; // PathName;
16029         } else if(p = strstr(opt->name, " -check ")) {
16030             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16031             opt->value = (def != 0);
16032             opt->type = CheckBox;
16033         } else if(p = strstr(opt->name, " -combo ")) {
16034             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16035             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16036             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16037             opt->value = n = 0;
16038             while(q = StrStr(q, " /// ")) {
16039                 n++; *q = 0;    // count choices, and null-terminate each of them
16040                 q += 5;
16041                 if(*q == '*') { // remember default, which is marked with * prefix
16042                     q++;
16043                     opt->value = n;
16044                 }
16045                 cps->comboList[cps->comboCnt++] = q;
16046             }
16047             cps->comboList[cps->comboCnt++] = NULL;
16048             opt->max = n + 1;
16049             opt->type = ComboBox;
16050         } else if(p = strstr(opt->name, " -button")) {
16051             opt->type = Button;
16052         } else if(p = strstr(opt->name, " -save")) {
16053             opt->type = SaveButton;
16054         } else return FALSE;
16055         *p = 0; // terminate option name
16056         // now look if the command-line options define a setting for this engine option.
16057         if(cps->optionSettings && cps->optionSettings[0])
16058             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16059         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16060           snprintf(buf, MSG_SIZ, "option %s", p);
16061                 if(p = strstr(buf, ",")) *p = 0;
16062                 if(q = strchr(buf, '=')) switch(opt->type) {
16063                     case ComboBox:
16064                         for(n=0; n<opt->max; n++)
16065                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16066                         break;
16067                     case TextBox:
16068                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16069                         break;
16070                     case Spin:
16071                     case CheckBox:
16072                         opt->value = atoi(q+1);
16073                     default:
16074                         break;
16075                 }
16076                 strcat(buf, "\n");
16077                 SendToProgram(buf, cps);
16078         }
16079         return TRUE;
16080 }
16081
16082 void
16083 FeatureDone (ChessProgramState *cps, int val)
16084 {
16085   DelayedEventCallback cb = GetDelayedEvent();
16086   if ((cb == InitBackEnd3 && cps == &first) ||
16087       (cb == SettingsMenuIfReady && cps == &second) ||
16088       (cb == LoadEngine) ||
16089       (cb == TwoMachinesEventIfReady)) {
16090     CancelDelayedEvent();
16091     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16092   }
16093   cps->initDone = val;
16094   if(val) cps->reload = FALSE;
16095 }
16096
16097 /* Parse feature command from engine */
16098 void
16099 ParseFeatures (char *args, ChessProgramState *cps)
16100 {
16101   char *p = args;
16102   char *q = NULL;
16103   int val;
16104   char buf[MSG_SIZ];
16105
16106   for (;;) {
16107     while (*p == ' ') p++;
16108     if (*p == NULLCHAR) return;
16109
16110     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16111     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16112     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16113     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16114     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16115     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16116     if (BoolFeature(&p, "reuse", &val, cps)) {
16117       /* Engine can disable reuse, but can't enable it if user said no */
16118       if (!val) cps->reuse = FALSE;
16119       continue;
16120     }
16121     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16122     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16123       if (gameMode == TwoMachinesPlay) {
16124         DisplayTwoMachinesTitle();
16125       } else {
16126         DisplayTitle("");
16127       }
16128       continue;
16129     }
16130     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16131     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16132     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16133     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16134     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16135     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16136     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16137     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16138     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16139     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16140     if (IntFeature(&p, "done", &val, cps)) {
16141       FeatureDone(cps, val);
16142       continue;
16143     }
16144     /* Added by Tord: */
16145     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16146     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16147     /* End of additions by Tord */
16148
16149     /* [HGM] added features: */
16150     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16151     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16152     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16153     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16154     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16155     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16156     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16157         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16158         FREE(cps->option[cps->nrOptions].name);
16159         cps->option[cps->nrOptions].name = q; q = NULL;
16160         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16161           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16162             SendToProgram(buf, cps);
16163             continue;
16164         }
16165         if(cps->nrOptions >= MAX_OPTIONS) {
16166             cps->nrOptions--;
16167             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16168             DisplayError(buf, 0);
16169         }
16170         continue;
16171     }
16172     /* End of additions by HGM */
16173
16174     /* unknown feature: complain and skip */
16175     q = p;
16176     while (*q && *q != '=') q++;
16177     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16178     SendToProgram(buf, cps);
16179     p = q;
16180     if (*p == '=') {
16181       p++;
16182       if (*p == '\"') {
16183         p++;
16184         while (*p && *p != '\"') p++;
16185         if (*p == '\"') p++;
16186       } else {
16187         while (*p && *p != ' ') p++;
16188       }
16189     }
16190   }
16191
16192 }
16193
16194 void
16195 PeriodicUpdatesEvent (int newState)
16196 {
16197     if (newState == appData.periodicUpdates)
16198       return;
16199
16200     appData.periodicUpdates=newState;
16201
16202     /* Display type changes, so update it now */
16203 //    DisplayAnalysis();
16204
16205     /* Get the ball rolling again... */
16206     if (newState) {
16207         AnalysisPeriodicEvent(1);
16208         StartAnalysisClock();
16209     }
16210 }
16211
16212 void
16213 PonderNextMoveEvent (int newState)
16214 {
16215     if (newState == appData.ponderNextMove) return;
16216     if (gameMode == EditPosition) EditPositionDone(TRUE);
16217     if (newState) {
16218         SendToProgram("hard\n", &first);
16219         if (gameMode == TwoMachinesPlay) {
16220             SendToProgram("hard\n", &second);
16221         }
16222     } else {
16223         SendToProgram("easy\n", &first);
16224         thinkOutput[0] = NULLCHAR;
16225         if (gameMode == TwoMachinesPlay) {
16226             SendToProgram("easy\n", &second);
16227         }
16228     }
16229     appData.ponderNextMove = newState;
16230 }
16231
16232 void
16233 NewSettingEvent (int option, int *feature, char *command, int value)
16234 {
16235     char buf[MSG_SIZ];
16236
16237     if (gameMode == EditPosition) EditPositionDone(TRUE);
16238     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16239     if(feature == NULL || *feature) SendToProgram(buf, &first);
16240     if (gameMode == TwoMachinesPlay) {
16241         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16242     }
16243 }
16244
16245 void
16246 ShowThinkingEvent ()
16247 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16248 {
16249     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16250     int newState = appData.showThinking
16251         // [HGM] thinking: other features now need thinking output as well
16252         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16253
16254     if (oldState == newState) return;
16255     oldState = newState;
16256     if (gameMode == EditPosition) EditPositionDone(TRUE);
16257     if (oldState) {
16258         SendToProgram("post\n", &first);
16259         if (gameMode == TwoMachinesPlay) {
16260             SendToProgram("post\n", &second);
16261         }
16262     } else {
16263         SendToProgram("nopost\n", &first);
16264         thinkOutput[0] = NULLCHAR;
16265         if (gameMode == TwoMachinesPlay) {
16266             SendToProgram("nopost\n", &second);
16267         }
16268     }
16269 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16270 }
16271
16272 void
16273 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16274 {
16275   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16276   if (pr == NoProc) return;
16277   AskQuestion(title, question, replyPrefix, pr);
16278 }
16279
16280 void
16281 TypeInEvent (char firstChar)
16282 {
16283     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16284         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16285         gameMode == AnalyzeMode || gameMode == EditGame ||
16286         gameMode == EditPosition || gameMode == IcsExamining ||
16287         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16288         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16289                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16290                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16291         gameMode == Training) PopUpMoveDialog(firstChar);
16292 }
16293
16294 void
16295 TypeInDoneEvent (char *move)
16296 {
16297         Board board;
16298         int n, fromX, fromY, toX, toY;
16299         char promoChar;
16300         ChessMove moveType;
16301
16302         // [HGM] FENedit
16303         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16304                 EditPositionPasteFEN(move);
16305                 return;
16306         }
16307         // [HGM] movenum: allow move number to be typed in any mode
16308         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16309           ToNrEvent(2*n-1);
16310           return;
16311         }
16312         // undocumented kludge: allow command-line option to be typed in!
16313         // (potentially fatal, and does not implement the effect of the option.)
16314         // should only be used for options that are values on which future decisions will be made,
16315         // and definitely not on options that would be used during initialization.
16316         if(strstr(move, "!!! -") == move) {
16317             ParseArgsFromString(move+4);
16318             return;
16319         }
16320
16321       if (gameMode != EditGame && currentMove != forwardMostMove &&
16322         gameMode != Training) {
16323         DisplayMoveError(_("Displayed move is not current"));
16324       } else {
16325         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16326           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16327         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16328         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16329           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16330           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16331         } else {
16332           DisplayMoveError(_("Could not parse move"));
16333         }
16334       }
16335 }
16336
16337 void
16338 DisplayMove (int moveNumber)
16339 {
16340     char message[MSG_SIZ];
16341     char res[MSG_SIZ];
16342     char cpThinkOutput[MSG_SIZ];
16343
16344     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16345
16346     if (moveNumber == forwardMostMove - 1 ||
16347         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16348
16349         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16350
16351         if (strchr(cpThinkOutput, '\n')) {
16352             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16353         }
16354     } else {
16355         *cpThinkOutput = NULLCHAR;
16356     }
16357
16358     /* [AS] Hide thinking from human user */
16359     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16360         *cpThinkOutput = NULLCHAR;
16361         if( thinkOutput[0] != NULLCHAR ) {
16362             int i;
16363
16364             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16365                 cpThinkOutput[i] = '.';
16366             }
16367             cpThinkOutput[i] = NULLCHAR;
16368             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16369         }
16370     }
16371
16372     if (moveNumber == forwardMostMove - 1 &&
16373         gameInfo.resultDetails != NULL) {
16374         if (gameInfo.resultDetails[0] == NULLCHAR) {
16375           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16376         } else {
16377           snprintf(res, MSG_SIZ, " {%s} %s",
16378                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16379         }
16380     } else {
16381         res[0] = NULLCHAR;
16382     }
16383
16384     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16385         DisplayMessage(res, cpThinkOutput);
16386     } else {
16387       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16388                 WhiteOnMove(moveNumber) ? " " : ".. ",
16389                 parseList[moveNumber], res);
16390         DisplayMessage(message, cpThinkOutput);
16391     }
16392 }
16393
16394 void
16395 DisplayComment (int moveNumber, char *text)
16396 {
16397     char title[MSG_SIZ];
16398
16399     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16400       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16401     } else {
16402       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16403               WhiteOnMove(moveNumber) ? " " : ".. ",
16404               parseList[moveNumber]);
16405     }
16406     if (text != NULL && (appData.autoDisplayComment || commentUp))
16407         CommentPopUp(title, text);
16408 }
16409
16410 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16411  * might be busy thinking or pondering.  It can be omitted if your
16412  * gnuchess is configured to stop thinking immediately on any user
16413  * input.  However, that gnuchess feature depends on the FIONREAD
16414  * ioctl, which does not work properly on some flavors of Unix.
16415  */
16416 void
16417 Attention (ChessProgramState *cps)
16418 {
16419 #if ATTENTION
16420     if (!cps->useSigint) return;
16421     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16422     switch (gameMode) {
16423       case MachinePlaysWhite:
16424       case MachinePlaysBlack:
16425       case TwoMachinesPlay:
16426       case IcsPlayingWhite:
16427       case IcsPlayingBlack:
16428       case AnalyzeMode:
16429       case AnalyzeFile:
16430         /* Skip if we know it isn't thinking */
16431         if (!cps->maybeThinking) return;
16432         if (appData.debugMode)
16433           fprintf(debugFP, "Interrupting %s\n", cps->which);
16434         InterruptChildProcess(cps->pr);
16435         cps->maybeThinking = FALSE;
16436         break;
16437       default:
16438         break;
16439     }
16440 #endif /*ATTENTION*/
16441 }
16442
16443 int
16444 CheckFlags ()
16445 {
16446     if (whiteTimeRemaining <= 0) {
16447         if (!whiteFlag) {
16448             whiteFlag = TRUE;
16449             if (appData.icsActive) {
16450                 if (appData.autoCallFlag &&
16451                     gameMode == IcsPlayingBlack && !blackFlag) {
16452                   SendToICS(ics_prefix);
16453                   SendToICS("flag\n");
16454                 }
16455             } else {
16456                 if (blackFlag) {
16457                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16458                 } else {
16459                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16460                     if (appData.autoCallFlag) {
16461                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16462                         return TRUE;
16463                     }
16464                 }
16465             }
16466         }
16467     }
16468     if (blackTimeRemaining <= 0) {
16469         if (!blackFlag) {
16470             blackFlag = TRUE;
16471             if (appData.icsActive) {
16472                 if (appData.autoCallFlag &&
16473                     gameMode == IcsPlayingWhite && !whiteFlag) {
16474                   SendToICS(ics_prefix);
16475                   SendToICS("flag\n");
16476                 }
16477             } else {
16478                 if (whiteFlag) {
16479                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16480                 } else {
16481                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16482                     if (appData.autoCallFlag) {
16483                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16484                         return TRUE;
16485                     }
16486                 }
16487             }
16488         }
16489     }
16490     return FALSE;
16491 }
16492
16493 void
16494 CheckTimeControl ()
16495 {
16496     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16497         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16498
16499     /*
16500      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16501      */
16502     if ( !WhiteOnMove(forwardMostMove) ) {
16503         /* White made time control */
16504         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16505         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16506         /* [HGM] time odds: correct new time quota for time odds! */
16507                                             / WhitePlayer()->timeOdds;
16508         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16509     } else {
16510         lastBlack -= blackTimeRemaining;
16511         /* Black made time control */
16512         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16513                                             / WhitePlayer()->other->timeOdds;
16514         lastWhite = whiteTimeRemaining;
16515     }
16516 }
16517
16518 void
16519 DisplayBothClocks ()
16520 {
16521     int wom = gameMode == EditPosition ?
16522       !blackPlaysFirst : WhiteOnMove(currentMove);
16523     DisplayWhiteClock(whiteTimeRemaining, wom);
16524     DisplayBlackClock(blackTimeRemaining, !wom);
16525 }
16526
16527
16528 /* Timekeeping seems to be a portability nightmare.  I think everyone
16529    has ftime(), but I'm really not sure, so I'm including some ifdefs
16530    to use other calls if you don't.  Clocks will be less accurate if
16531    you have neither ftime nor gettimeofday.
16532 */
16533
16534 /* VS 2008 requires the #include outside of the function */
16535 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16536 #include <sys/timeb.h>
16537 #endif
16538
16539 /* Get the current time as a TimeMark */
16540 void
16541 GetTimeMark (TimeMark *tm)
16542 {
16543 #if HAVE_GETTIMEOFDAY
16544
16545     struct timeval timeVal;
16546     struct timezone timeZone;
16547
16548     gettimeofday(&timeVal, &timeZone);
16549     tm->sec = (long) timeVal.tv_sec;
16550     tm->ms = (int) (timeVal.tv_usec / 1000L);
16551
16552 #else /*!HAVE_GETTIMEOFDAY*/
16553 #if HAVE_FTIME
16554
16555 // include <sys/timeb.h> / moved to just above start of function
16556     struct timeb timeB;
16557
16558     ftime(&timeB);
16559     tm->sec = (long) timeB.time;
16560     tm->ms = (int) timeB.millitm;
16561
16562 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16563     tm->sec = (long) time(NULL);
16564     tm->ms = 0;
16565 #endif
16566 #endif
16567 }
16568
16569 /* Return the difference in milliseconds between two
16570    time marks.  We assume the difference will fit in a long!
16571 */
16572 long
16573 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16574 {
16575     return 1000L*(tm2->sec - tm1->sec) +
16576            (long) (tm2->ms - tm1->ms);
16577 }
16578
16579
16580 /*
16581  * Code to manage the game clocks.
16582  *
16583  * In tournament play, black starts the clock and then white makes a move.
16584  * We give the human user a slight advantage if he is playing white---the
16585  * clocks don't run until he makes his first move, so it takes zero time.
16586  * Also, we don't account for network lag, so we could get out of sync
16587  * with GNU Chess's clock -- but then, referees are always right.
16588  */
16589
16590 static TimeMark tickStartTM;
16591 static long intendedTickLength;
16592
16593 long
16594 NextTickLength (long timeRemaining)
16595 {
16596     long nominalTickLength, nextTickLength;
16597
16598     if (timeRemaining > 0L && timeRemaining <= 10000L)
16599       nominalTickLength = 100L;
16600     else
16601       nominalTickLength = 1000L;
16602     nextTickLength = timeRemaining % nominalTickLength;
16603     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16604
16605     return nextTickLength;
16606 }
16607
16608 /* Adjust clock one minute up or down */
16609 void
16610 AdjustClock (Boolean which, int dir)
16611 {
16612     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16613     if(which) blackTimeRemaining += 60000*dir;
16614     else      whiteTimeRemaining += 60000*dir;
16615     DisplayBothClocks();
16616     adjustedClock = TRUE;
16617 }
16618
16619 /* Stop clocks and reset to a fresh time control */
16620 void
16621 ResetClocks ()
16622 {
16623     (void) StopClockTimer();
16624     if (appData.icsActive) {
16625         whiteTimeRemaining = blackTimeRemaining = 0;
16626     } else if (searchTime) {
16627         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16628         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16629     } else { /* [HGM] correct new time quote for time odds */
16630         whiteTC = blackTC = fullTimeControlString;
16631         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16632         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16633     }
16634     if (whiteFlag || blackFlag) {
16635         DisplayTitle("");
16636         whiteFlag = blackFlag = FALSE;
16637     }
16638     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16639     DisplayBothClocks();
16640     adjustedClock = FALSE;
16641 }
16642
16643 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16644
16645 /* Decrement running clock by amount of time that has passed */
16646 void
16647 DecrementClocks ()
16648 {
16649     long timeRemaining;
16650     long lastTickLength, fudge;
16651     TimeMark now;
16652
16653     if (!appData.clockMode) return;
16654     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16655
16656     GetTimeMark(&now);
16657
16658     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16659
16660     /* Fudge if we woke up a little too soon */
16661     fudge = intendedTickLength - lastTickLength;
16662     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16663
16664     if (WhiteOnMove(forwardMostMove)) {
16665         if(whiteNPS >= 0) lastTickLength = 0;
16666         timeRemaining = whiteTimeRemaining -= lastTickLength;
16667         if(timeRemaining < 0 && !appData.icsActive) {
16668             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16669             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16670                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16671                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16672             }
16673         }
16674         DisplayWhiteClock(whiteTimeRemaining - fudge,
16675                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16676     } else {
16677         if(blackNPS >= 0) lastTickLength = 0;
16678         timeRemaining = blackTimeRemaining -= lastTickLength;
16679         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16680             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16681             if(suddenDeath) {
16682                 blackStartMove = forwardMostMove;
16683                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16684             }
16685         }
16686         DisplayBlackClock(blackTimeRemaining - fudge,
16687                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16688     }
16689     if (CheckFlags()) return;
16690
16691     if(twoBoards) { // count down secondary board's clocks as well
16692         activePartnerTime -= lastTickLength;
16693         partnerUp = 1;
16694         if(activePartner == 'W')
16695             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16696         else
16697             DisplayBlackClock(activePartnerTime, TRUE);
16698         partnerUp = 0;
16699     }
16700
16701     tickStartTM = now;
16702     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16703     StartClockTimer(intendedTickLength);
16704
16705     /* if the time remaining has fallen below the alarm threshold, sound the
16706      * alarm. if the alarm has sounded and (due to a takeback or time control
16707      * with increment) the time remaining has increased to a level above the
16708      * threshold, reset the alarm so it can sound again.
16709      */
16710
16711     if (appData.icsActive && appData.icsAlarm) {
16712
16713         /* make sure we are dealing with the user's clock */
16714         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16715                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16716            )) return;
16717
16718         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16719             alarmSounded = FALSE;
16720         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16721             PlayAlarmSound();
16722             alarmSounded = TRUE;
16723         }
16724     }
16725 }
16726
16727
16728 /* A player has just moved, so stop the previously running
16729    clock and (if in clock mode) start the other one.
16730    We redisplay both clocks in case we're in ICS mode, because
16731    ICS gives us an update to both clocks after every move.
16732    Note that this routine is called *after* forwardMostMove
16733    is updated, so the last fractional tick must be subtracted
16734    from the color that is *not* on move now.
16735 */
16736 void
16737 SwitchClocks (int newMoveNr)
16738 {
16739     long lastTickLength;
16740     TimeMark now;
16741     int flagged = FALSE;
16742
16743     GetTimeMark(&now);
16744
16745     if (StopClockTimer() && appData.clockMode) {
16746         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16747         if (!WhiteOnMove(forwardMostMove)) {
16748             if(blackNPS >= 0) lastTickLength = 0;
16749             blackTimeRemaining -= lastTickLength;
16750            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16751 //         if(pvInfoList[forwardMostMove].time == -1)
16752                  pvInfoList[forwardMostMove].time =               // use GUI time
16753                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16754         } else {
16755            if(whiteNPS >= 0) lastTickLength = 0;
16756            whiteTimeRemaining -= lastTickLength;
16757            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16758 //         if(pvInfoList[forwardMostMove].time == -1)
16759                  pvInfoList[forwardMostMove].time =
16760                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16761         }
16762         flagged = CheckFlags();
16763     }
16764     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16765     CheckTimeControl();
16766
16767     if (flagged || !appData.clockMode) return;
16768
16769     switch (gameMode) {
16770       case MachinePlaysBlack:
16771       case MachinePlaysWhite:
16772       case BeginningOfGame:
16773         if (pausing) return;
16774         break;
16775
16776       case EditGame:
16777       case PlayFromGameFile:
16778       case IcsExamining:
16779         return;
16780
16781       default:
16782         break;
16783     }
16784
16785     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16786         if(WhiteOnMove(forwardMostMove))
16787              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16788         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16789     }
16790
16791     tickStartTM = now;
16792     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16793       whiteTimeRemaining : blackTimeRemaining);
16794     StartClockTimer(intendedTickLength);
16795 }
16796
16797
16798 /* Stop both clocks */
16799 void
16800 StopClocks ()
16801 {
16802     long lastTickLength;
16803     TimeMark now;
16804
16805     if (!StopClockTimer()) return;
16806     if (!appData.clockMode) return;
16807
16808     GetTimeMark(&now);
16809
16810     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16811     if (WhiteOnMove(forwardMostMove)) {
16812         if(whiteNPS >= 0) lastTickLength = 0;
16813         whiteTimeRemaining -= lastTickLength;
16814         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16815     } else {
16816         if(blackNPS >= 0) lastTickLength = 0;
16817         blackTimeRemaining -= lastTickLength;
16818         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16819     }
16820     CheckFlags();
16821 }
16822
16823 /* Start clock of player on move.  Time may have been reset, so
16824    if clock is already running, stop and restart it. */
16825 void
16826 StartClocks ()
16827 {
16828     (void) StopClockTimer(); /* in case it was running already */
16829     DisplayBothClocks();
16830     if (CheckFlags()) return;
16831
16832     if (!appData.clockMode) return;
16833     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16834
16835     GetTimeMark(&tickStartTM);
16836     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16837       whiteTimeRemaining : blackTimeRemaining);
16838
16839    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16840     whiteNPS = blackNPS = -1;
16841     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16842        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16843         whiteNPS = first.nps;
16844     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16845        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16846         blackNPS = first.nps;
16847     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16848         whiteNPS = second.nps;
16849     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16850         blackNPS = second.nps;
16851     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16852
16853     StartClockTimer(intendedTickLength);
16854 }
16855
16856 char *
16857 TimeString (long ms)
16858 {
16859     long second, minute, hour, day;
16860     char *sign = "";
16861     static char buf[32];
16862
16863     if (ms > 0 && ms <= 9900) {
16864       /* convert milliseconds to tenths, rounding up */
16865       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16866
16867       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16868       return buf;
16869     }
16870
16871     /* convert milliseconds to seconds, rounding up */
16872     /* use floating point to avoid strangeness of integer division
16873        with negative dividends on many machines */
16874     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16875
16876     if (second < 0) {
16877         sign = "-";
16878         second = -second;
16879     }
16880
16881     day = second / (60 * 60 * 24);
16882     second = second % (60 * 60 * 24);
16883     hour = second / (60 * 60);
16884     second = second % (60 * 60);
16885     minute = second / 60;
16886     second = second % 60;
16887
16888     if (day > 0)
16889       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16890               sign, day, hour, minute, second);
16891     else if (hour > 0)
16892       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16893     else
16894       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16895
16896     return buf;
16897 }
16898
16899
16900 /*
16901  * This is necessary because some C libraries aren't ANSI C compliant yet.
16902  */
16903 char *
16904 StrStr (char *string, char *match)
16905 {
16906     int i, length;
16907
16908     length = strlen(match);
16909
16910     for (i = strlen(string) - length; i >= 0; i--, string++)
16911       if (!strncmp(match, string, length))
16912         return string;
16913
16914     return NULL;
16915 }
16916
16917 char *
16918 StrCaseStr (char *string, char *match)
16919 {
16920     int i, j, length;
16921
16922     length = strlen(match);
16923
16924     for (i = strlen(string) - length; i >= 0; i--, string++) {
16925         for (j = 0; j < length; j++) {
16926             if (ToLower(match[j]) != ToLower(string[j]))
16927               break;
16928         }
16929         if (j == length) return string;
16930     }
16931
16932     return NULL;
16933 }
16934
16935 #ifndef _amigados
16936 int
16937 StrCaseCmp (char *s1, char *s2)
16938 {
16939     char c1, c2;
16940
16941     for (;;) {
16942         c1 = ToLower(*s1++);
16943         c2 = ToLower(*s2++);
16944         if (c1 > c2) return 1;
16945         if (c1 < c2) return -1;
16946         if (c1 == NULLCHAR) return 0;
16947     }
16948 }
16949
16950
16951 int
16952 ToLower (int c)
16953 {
16954     return isupper(c) ? tolower(c) : c;
16955 }
16956
16957
16958 int
16959 ToUpper (int c)
16960 {
16961     return islower(c) ? toupper(c) : c;
16962 }
16963 #endif /* !_amigados    */
16964
16965 char *
16966 StrSave (char *s)
16967 {
16968   char *ret;
16969
16970   if ((ret = (char *) malloc(strlen(s) + 1)))
16971     {
16972       safeStrCpy(ret, s, strlen(s)+1);
16973     }
16974   return ret;
16975 }
16976
16977 char *
16978 StrSavePtr (char *s, char **savePtr)
16979 {
16980     if (*savePtr) {
16981         free(*savePtr);
16982     }
16983     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16984       safeStrCpy(*savePtr, s, strlen(s)+1);
16985     }
16986     return(*savePtr);
16987 }
16988
16989 char *
16990 PGNDate ()
16991 {
16992     time_t clock;
16993     struct tm *tm;
16994     char buf[MSG_SIZ];
16995
16996     clock = time((time_t *)NULL);
16997     tm = localtime(&clock);
16998     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16999             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17000     return StrSave(buf);
17001 }
17002
17003
17004 char *
17005 PositionToFEN (int move, char *overrideCastling)
17006 {
17007     int i, j, fromX, fromY, toX, toY;
17008     int whiteToPlay;
17009     char buf[MSG_SIZ];
17010     char *p, *q;
17011     int emptycount;
17012     ChessSquare piece;
17013
17014     whiteToPlay = (gameMode == EditPosition) ?
17015       !blackPlaysFirst : (move % 2 == 0);
17016     p = buf;
17017
17018     /* Piece placement data */
17019     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17020         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17021         emptycount = 0;
17022         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17023             if (boards[move][i][j] == EmptySquare) {
17024                 emptycount++;
17025             } else { ChessSquare piece = boards[move][i][j];
17026                 if (emptycount > 0) {
17027                     if(emptycount<10) /* [HGM] can be >= 10 */
17028                         *p++ = '0' + emptycount;
17029                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17030                     emptycount = 0;
17031                 }
17032                 if(PieceToChar(piece) == '+') {
17033                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17034                     *p++ = '+';
17035                     piece = (ChessSquare)(DEMOTED piece);
17036                 }
17037                 *p++ = PieceToChar(piece);
17038                 if(p[-1] == '~') {
17039                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17040                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17041                     *p++ = '~';
17042                 }
17043             }
17044         }
17045         if (emptycount > 0) {
17046             if(emptycount<10) /* [HGM] can be >= 10 */
17047                 *p++ = '0' + emptycount;
17048             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17049             emptycount = 0;
17050         }
17051         *p++ = '/';
17052     }
17053     *(p - 1) = ' ';
17054
17055     /* [HGM] print Crazyhouse or Shogi holdings */
17056     if( gameInfo.holdingsWidth ) {
17057         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17058         q = p;
17059         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17060             piece = boards[move][i][BOARD_WIDTH-1];
17061             if( piece != EmptySquare )
17062               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17063                   *p++ = PieceToChar(piece);
17064         }
17065         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17066             piece = boards[move][BOARD_HEIGHT-i-1][0];
17067             if( piece != EmptySquare )
17068               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17069                   *p++ = PieceToChar(piece);
17070         }
17071
17072         if( q == p ) *p++ = '-';
17073         *p++ = ']';
17074         *p++ = ' ';
17075     }
17076
17077     /* Active color */
17078     *p++ = whiteToPlay ? 'w' : 'b';
17079     *p++ = ' ';
17080
17081   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17082     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17083   } else {
17084   if(nrCastlingRights) {
17085      q = p;
17086      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17087        /* [HGM] write directly from rights */
17088            if(boards[move][CASTLING][2] != NoRights &&
17089               boards[move][CASTLING][0] != NoRights   )
17090                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17091            if(boards[move][CASTLING][2] != NoRights &&
17092               boards[move][CASTLING][1] != NoRights   )
17093                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17094            if(boards[move][CASTLING][5] != NoRights &&
17095               boards[move][CASTLING][3] != NoRights   )
17096                 *p++ = boards[move][CASTLING][3] + AAA;
17097            if(boards[move][CASTLING][5] != NoRights &&
17098               boards[move][CASTLING][4] != NoRights   )
17099                 *p++ = boards[move][CASTLING][4] + AAA;
17100      } else {
17101
17102         /* [HGM] write true castling rights */
17103         if( nrCastlingRights == 6 ) {
17104             int q, k=0;
17105             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17106                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17107             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17108                  boards[move][CASTLING][2] != NoRights  );
17109             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17110                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17111                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17112                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17113                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17114             }
17115             if(q) *p++ = 'Q';
17116             k = 0;
17117             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17118                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17119             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17120                  boards[move][CASTLING][5] != NoRights  );
17121             if(gameInfo.variant == VariantSChess) {
17122                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17123                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17124                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17125                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17126             }
17127             if(q) *p++ = 'q';
17128         }
17129      }
17130      if (q == p) *p++ = '-'; /* No castling rights */
17131      *p++ = ' ';
17132   }
17133
17134   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17135      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17136     /* En passant target square */
17137     if (move > backwardMostMove) {
17138         fromX = moveList[move - 1][0] - AAA;
17139         fromY = moveList[move - 1][1] - ONE;
17140         toX = moveList[move - 1][2] - AAA;
17141         toY = moveList[move - 1][3] - ONE;
17142         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17143             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17144             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17145             fromX == toX) {
17146             /* 2-square pawn move just happened */
17147             *p++ = toX + AAA;
17148             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17149         } else {
17150             *p++ = '-';
17151         }
17152     } else if(move == backwardMostMove) {
17153         // [HGM] perhaps we should always do it like this, and forget the above?
17154         if((signed char)boards[move][EP_STATUS] >= 0) {
17155             *p++ = boards[move][EP_STATUS] + AAA;
17156             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17157         } else {
17158             *p++ = '-';
17159         }
17160     } else {
17161         *p++ = '-';
17162     }
17163     *p++ = ' ';
17164   }
17165   }
17166
17167     /* [HGM] find reversible plies */
17168     {   int i = 0, j=move;
17169
17170         if (appData.debugMode) { int k;
17171             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17172             for(k=backwardMostMove; k<=forwardMostMove; k++)
17173                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17174
17175         }
17176
17177         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17178         if( j == backwardMostMove ) i += initialRulePlies;
17179         sprintf(p, "%d ", i);
17180         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17181     }
17182     /* Fullmove number */
17183     sprintf(p, "%d", (move / 2) + 1);
17184
17185     return StrSave(buf);
17186 }
17187
17188 Boolean
17189 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
17190 {
17191     int i, j;
17192     char *p, c;
17193     int emptycount, virgin[BOARD_FILES];
17194     ChessSquare piece;
17195
17196     p = fen;
17197
17198     /* [HGM] by default clear Crazyhouse holdings, if present */
17199     if(gameInfo.holdingsWidth) {
17200        for(i=0; i<BOARD_HEIGHT; i++) {
17201            board[i][0]             = EmptySquare; /* black holdings */
17202            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17203            board[i][1]             = (ChessSquare) 0; /* black counts */
17204            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17205        }
17206     }
17207
17208     /* Piece placement data */
17209     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17210         j = 0;
17211         for (;;) {
17212             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
17213                 if (*p == '/') p++;
17214                 emptycount = gameInfo.boardWidth - j;
17215                 while (emptycount--)
17216                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17217                 break;
17218 #if(BOARD_FILES >= 10)
17219             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17220                 p++; emptycount=10;
17221                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17222                 while (emptycount--)
17223                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17224 #endif
17225             } else if (isdigit(*p)) {
17226                 emptycount = *p++ - '0';
17227                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17228                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17229                 while (emptycount--)
17230                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17231             } else if (*p == '+' || isalpha(*p)) {
17232                 if (j >= gameInfo.boardWidth) return FALSE;
17233                 if(*p=='+') {
17234                     piece = CharToPiece(*++p);
17235                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17236                     piece = (ChessSquare) (PROMOTED piece ); p++;
17237                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17238                 } else piece = CharToPiece(*p++);
17239
17240                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17241                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17242                     piece = (ChessSquare) (PROMOTED piece);
17243                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17244                     p++;
17245                 }
17246                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17247             } else {
17248                 return FALSE;
17249             }
17250         }
17251     }
17252     while (*p == '/' || *p == ' ') p++;
17253
17254     /* [HGM] look for Crazyhouse holdings here */
17255     while(*p==' ') p++;
17256     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17257         if(*p == '[') p++;
17258         if(*p == '-' ) p++; /* empty holdings */ else {
17259             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17260             /* if we would allow FEN reading to set board size, we would   */
17261             /* have to add holdings and shift the board read so far here   */
17262             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17263                 p++;
17264                 if((int) piece >= (int) BlackPawn ) {
17265                     i = (int)piece - (int)BlackPawn;
17266                     i = PieceToNumber((ChessSquare)i);
17267                     if( i >= gameInfo.holdingsSize ) return FALSE;
17268                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17269                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17270                 } else {
17271                     i = (int)piece - (int)WhitePawn;
17272                     i = PieceToNumber((ChessSquare)i);
17273                     if( i >= gameInfo.holdingsSize ) return FALSE;
17274                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17275                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17276                 }
17277             }
17278         }
17279         if(*p == ']') p++;
17280     }
17281
17282     while(*p == ' ') p++;
17283
17284     /* Active color */
17285     c = *p++;
17286     if(appData.colorNickNames) {
17287       if( c == appData.colorNickNames[0] ) c = 'w'; else
17288       if( c == appData.colorNickNames[1] ) c = 'b';
17289     }
17290     switch (c) {
17291       case 'w':
17292         *blackPlaysFirst = FALSE;
17293         break;
17294       case 'b':
17295         *blackPlaysFirst = TRUE;
17296         break;
17297       default:
17298         return FALSE;
17299     }
17300
17301     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17302     /* return the extra info in global variiables             */
17303
17304     /* set defaults in case FEN is incomplete */
17305     board[EP_STATUS] = EP_UNKNOWN;
17306     for(i=0; i<nrCastlingRights; i++ ) {
17307         board[CASTLING][i] =
17308             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17309     }   /* assume possible unless obviously impossible */
17310     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17311     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17312     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17313                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17314     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17315     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17316     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17317                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17318     FENrulePlies = 0;
17319
17320     while(*p==' ') p++;
17321     if(nrCastlingRights) {
17322       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17323       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17324           /* castling indicator present, so default becomes no castlings */
17325           for(i=0; i<nrCastlingRights; i++ ) {
17326                  board[CASTLING][i] = NoRights;
17327           }
17328       }
17329       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17330              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17331              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17332              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17333         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17334
17335         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17336             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17337             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17338         }
17339         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17340             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17341         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17342                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17343         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17344                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17345         switch(c) {
17346           case'K':
17347               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17348               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17349               board[CASTLING][2] = whiteKingFile;
17350               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17351               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17352               break;
17353           case'Q':
17354               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17355               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17356               board[CASTLING][2] = whiteKingFile;
17357               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17358               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17359               break;
17360           case'k':
17361               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17362               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17363               board[CASTLING][5] = blackKingFile;
17364               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17365               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17366               break;
17367           case'q':
17368               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17369               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17370               board[CASTLING][5] = blackKingFile;
17371               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17372               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17373           case '-':
17374               break;
17375           default: /* FRC castlings */
17376               if(c >= 'a') { /* black rights */
17377                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17378                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17379                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17380                   if(i == BOARD_RGHT) break;
17381                   board[CASTLING][5] = i;
17382                   c -= AAA;
17383                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17384                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17385                   if(c > i)
17386                       board[CASTLING][3] = c;
17387                   else
17388                       board[CASTLING][4] = c;
17389               } else { /* white rights */
17390                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17391                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17392                     if(board[0][i] == WhiteKing) break;
17393                   if(i == BOARD_RGHT) break;
17394                   board[CASTLING][2] = i;
17395                   c -= AAA - 'a' + 'A';
17396                   if(board[0][c] >= WhiteKing) break;
17397                   if(c > i)
17398                       board[CASTLING][0] = c;
17399                   else
17400                       board[CASTLING][1] = c;
17401               }
17402         }
17403       }
17404       for(i=0; i<nrCastlingRights; i++)
17405         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17406       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17407     if (appData.debugMode) {
17408         fprintf(debugFP, "FEN castling rights:");
17409         for(i=0; i<nrCastlingRights; i++)
17410         fprintf(debugFP, " %d", board[CASTLING][i]);
17411         fprintf(debugFP, "\n");
17412     }
17413
17414       while(*p==' ') p++;
17415     }
17416
17417     /* read e.p. field in games that know e.p. capture */
17418     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17419        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17420       if(*p=='-') {
17421         p++; board[EP_STATUS] = EP_NONE;
17422       } else {
17423          char c = *p++ - AAA;
17424
17425          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17426          if(*p >= '0' && *p <='9') p++;
17427          board[EP_STATUS] = c;
17428       }
17429     }
17430
17431
17432     if(sscanf(p, "%d", &i) == 1) {
17433         FENrulePlies = i; /* 50-move ply counter */
17434         /* (The move number is still ignored)    */
17435     }
17436
17437     return TRUE;
17438 }
17439
17440 void
17441 EditPositionPasteFEN (char *fen)
17442 {
17443   if (fen != NULL) {
17444     Board initial_position;
17445
17446     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17447       DisplayError(_("Bad FEN position in clipboard"), 0);
17448       return ;
17449     } else {
17450       int savedBlackPlaysFirst = blackPlaysFirst;
17451       EditPositionEvent();
17452       blackPlaysFirst = savedBlackPlaysFirst;
17453       CopyBoard(boards[0], initial_position);
17454       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17455       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17456       DisplayBothClocks();
17457       DrawPosition(FALSE, boards[currentMove]);
17458     }
17459   }
17460 }
17461
17462 static char cseq[12] = "\\   ";
17463
17464 Boolean
17465 set_cont_sequence (char *new_seq)
17466 {
17467     int len;
17468     Boolean ret;
17469
17470     // handle bad attempts to set the sequence
17471         if (!new_seq)
17472                 return 0; // acceptable error - no debug
17473
17474     len = strlen(new_seq);
17475     ret = (len > 0) && (len < sizeof(cseq));
17476     if (ret)
17477       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17478     else if (appData.debugMode)
17479       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17480     return ret;
17481 }
17482
17483 /*
17484     reformat a source message so words don't cross the width boundary.  internal
17485     newlines are not removed.  returns the wrapped size (no null character unless
17486     included in source message).  If dest is NULL, only calculate the size required
17487     for the dest buffer.  lp argument indicats line position upon entry, and it's
17488     passed back upon exit.
17489 */
17490 int
17491 wrap (char *dest, char *src, int count, int width, int *lp)
17492 {
17493     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17494
17495     cseq_len = strlen(cseq);
17496     old_line = line = *lp;
17497     ansi = len = clen = 0;
17498
17499     for (i=0; i < count; i++)
17500     {
17501         if (src[i] == '\033')
17502             ansi = 1;
17503
17504         // if we hit the width, back up
17505         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17506         {
17507             // store i & len in case the word is too long
17508             old_i = i, old_len = len;
17509
17510             // find the end of the last word
17511             while (i && src[i] != ' ' && src[i] != '\n')
17512             {
17513                 i--;
17514                 len--;
17515             }
17516
17517             // word too long?  restore i & len before splitting it
17518             if ((old_i-i+clen) >= width)
17519             {
17520                 i = old_i;
17521                 len = old_len;
17522             }
17523
17524             // extra space?
17525             if (i && src[i-1] == ' ')
17526                 len--;
17527
17528             if (src[i] != ' ' && src[i] != '\n')
17529             {
17530                 i--;
17531                 if (len)
17532                     len--;
17533             }
17534
17535             // now append the newline and continuation sequence
17536             if (dest)
17537                 dest[len] = '\n';
17538             len++;
17539             if (dest)
17540                 strncpy(dest+len, cseq, cseq_len);
17541             len += cseq_len;
17542             line = cseq_len;
17543             clen = cseq_len;
17544             continue;
17545         }
17546
17547         if (dest)
17548             dest[len] = src[i];
17549         len++;
17550         if (!ansi)
17551             line++;
17552         if (src[i] == '\n')
17553             line = 0;
17554         if (src[i] == 'm')
17555             ansi = 0;
17556     }
17557     if (dest && appData.debugMode)
17558     {
17559         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17560             count, width, line, len, *lp);
17561         show_bytes(debugFP, src, count);
17562         fprintf(debugFP, "\ndest: ");
17563         show_bytes(debugFP, dest, len);
17564         fprintf(debugFP, "\n");
17565     }
17566     *lp = dest ? line : old_line;
17567
17568     return len;
17569 }
17570
17571 // [HGM] vari: routines for shelving variations
17572 Boolean modeRestore = FALSE;
17573
17574 void
17575 PushInner (int firstMove, int lastMove)
17576 {
17577         int i, j, nrMoves = lastMove - firstMove;
17578
17579         // push current tail of game on stack
17580         savedResult[storedGames] = gameInfo.result;
17581         savedDetails[storedGames] = gameInfo.resultDetails;
17582         gameInfo.resultDetails = NULL;
17583         savedFirst[storedGames] = firstMove;
17584         savedLast [storedGames] = lastMove;
17585         savedFramePtr[storedGames] = framePtr;
17586         framePtr -= nrMoves; // reserve space for the boards
17587         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17588             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17589             for(j=0; j<MOVE_LEN; j++)
17590                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17591             for(j=0; j<2*MOVE_LEN; j++)
17592                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17593             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17594             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17595             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17596             pvInfoList[firstMove+i-1].depth = 0;
17597             commentList[framePtr+i] = commentList[firstMove+i];
17598             commentList[firstMove+i] = NULL;
17599         }
17600
17601         storedGames++;
17602         forwardMostMove = firstMove; // truncate game so we can start variation
17603 }
17604
17605 void
17606 PushTail (int firstMove, int lastMove)
17607 {
17608         if(appData.icsActive) { // only in local mode
17609                 forwardMostMove = currentMove; // mimic old ICS behavior
17610                 return;
17611         }
17612         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17613
17614         PushInner(firstMove, lastMove);
17615         if(storedGames == 1) GreyRevert(FALSE);
17616         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17617 }
17618
17619 void
17620 PopInner (Boolean annotate)
17621 {
17622         int i, j, nrMoves;
17623         char buf[8000], moveBuf[20];
17624
17625         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17626         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17627         nrMoves = savedLast[storedGames] - currentMove;
17628         if(annotate) {
17629                 int cnt = 10;
17630                 if(!WhiteOnMove(currentMove))
17631                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17632                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17633                 for(i=currentMove; i<forwardMostMove; i++) {
17634                         if(WhiteOnMove(i))
17635                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17636                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17637                         strcat(buf, moveBuf);
17638                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17639                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17640                 }
17641                 strcat(buf, ")");
17642         }
17643         for(i=1; i<=nrMoves; i++) { // copy last variation back
17644             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17645             for(j=0; j<MOVE_LEN; j++)
17646                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17647             for(j=0; j<2*MOVE_LEN; j++)
17648                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17649             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17650             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17651             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17652             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17653             commentList[currentMove+i] = commentList[framePtr+i];
17654             commentList[framePtr+i] = NULL;
17655         }
17656         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17657         framePtr = savedFramePtr[storedGames];
17658         gameInfo.result = savedResult[storedGames];
17659         if(gameInfo.resultDetails != NULL) {
17660             free(gameInfo.resultDetails);
17661       }
17662         gameInfo.resultDetails = savedDetails[storedGames];
17663         forwardMostMove = currentMove + nrMoves;
17664 }
17665
17666 Boolean
17667 PopTail (Boolean annotate)
17668 {
17669         if(appData.icsActive) return FALSE; // only in local mode
17670         if(!storedGames) return FALSE; // sanity
17671         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17672
17673         PopInner(annotate);
17674         if(currentMove < forwardMostMove) ForwardEvent(); else
17675         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17676
17677         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17678         return TRUE;
17679 }
17680
17681 void
17682 CleanupTail ()
17683 {       // remove all shelved variations
17684         int i;
17685         for(i=0; i<storedGames; i++) {
17686             if(savedDetails[i])
17687                 free(savedDetails[i]);
17688             savedDetails[i] = NULL;
17689         }
17690         for(i=framePtr; i<MAX_MOVES; i++) {
17691                 if(commentList[i]) free(commentList[i]);
17692                 commentList[i] = NULL;
17693         }
17694         framePtr = MAX_MOVES-1;
17695         storedGames = 0;
17696 }
17697
17698 void
17699 LoadVariation (int index, char *text)
17700 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17701         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17702         int level = 0, move;
17703
17704         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17705         // first find outermost bracketing variation
17706         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17707             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17708                 if(*p == '{') wait = '}'; else
17709                 if(*p == '[') wait = ']'; else
17710                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17711                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17712             }
17713             if(*p == wait) wait = NULLCHAR; // closing ]} found
17714             p++;
17715         }
17716         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17717         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17718         end[1] = NULLCHAR; // clip off comment beyond variation
17719         ToNrEvent(currentMove-1);
17720         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17721         // kludge: use ParsePV() to append variation to game
17722         move = currentMove;
17723         ParsePV(start, TRUE, TRUE);
17724         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17725         ClearPremoveHighlights();
17726         CommentPopDown();
17727         ToNrEvent(currentMove+1);
17728 }
17729
17730 void
17731 LoadTheme ()
17732 {
17733     char *p, *q, buf[MSG_SIZ];
17734     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
17735         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
17736         ParseArgsFromString(buf);
17737         ActivateTheme(TRUE); // also redo colors
17738         return;
17739     }
17740     p = nickName;
17741     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
17742     {
17743         int len;
17744         q = appData.themeNames;
17745         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
17746       if(appData.useBitmaps) {
17747         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
17748                 appData.liteBackTextureFile, appData.darkBackTextureFile,
17749                 appData.liteBackTextureMode,
17750                 appData.darkBackTextureMode );
17751       } else {
17752         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
17753                 Col2Text(2),   // lightSquareColor
17754                 Col2Text(3) ); // darkSquareColor
17755       }
17756       if(appData.useBorder) {
17757         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
17758                 appData.border);
17759       } else {
17760         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
17761       }
17762       if(appData.useFont) {
17763         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
17764                 appData.renderPiecesWithFont,
17765                 appData.fontToPieceTable,
17766                 Col2Text(9),    // appData.fontBackColorWhite
17767                 Col2Text(10) ); // appData.fontForeColorBlack
17768       } else {
17769         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
17770                 appData.pieceDirectory);
17771         if(!appData.pieceDirectory[0])
17772           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
17773                 Col2Text(0),   // whitePieceColor
17774                 Col2Text(1) ); // blackPieceColor
17775       }
17776       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
17777                 Col2Text(4),   // highlightSquareColor
17778                 Col2Text(5) ); // premoveHighlightColor
17779         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
17780         if(insert != q) insert[-1] = NULLCHAR;
17781         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
17782         if(q)   free(q);
17783     }
17784     ActivateTheme(FALSE);
17785 }