Keep track of virginity of back-rank pieces in variant seirawan
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey, controlKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
879         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
880
881 void
882 FloatToFront(char **list, char *engineLine)
883 {
884     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885     int i=0;
886     if(appData.recentEngines <= 0) return;
887     TidyProgramName(engineLine, "localhost", tidy+1);
888     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889     strncpy(buf+1, *list, MSG_SIZ-50);
890     if(p = strstr(buf, tidy)) { // tidy name appears in list
891         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892         while(*p++ = *++q); // squeeze out
893     }
894     strcat(tidy, buf+1); // put list behind tidy name
895     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897     ASSIGN(*list, tidy+1);
898 }
899
900 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
901
902 void
903 Load (ChessProgramState *cps, int i)
904 {
905     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
906     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
907         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
908         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
909         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
910         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
911         appData.firstProtocolVersion = PROTOVER;
912         ParseArgsFromString(buf);
913         SwapEngines(i);
914         ReplaceEngine(cps, i);
915         FloatToFront(&appData.recentEngineList, engineLine);
916         return;
917     }
918     p = engineName;
919     while(q = strchr(p, SLASH)) p = q+1;
920     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
921     if(engineDir[0] != NULLCHAR) {
922         ASSIGN(appData.directory[i], engineDir); p = engineName;
923     } else if(p != engineName) { // derive directory from engine path, when not given
924         p[-1] = 0;
925         ASSIGN(appData.directory[i], engineName);
926         p[-1] = SLASH;
927         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
928     } else { ASSIGN(appData.directory[i], "."); }
929     if(params[0]) {
930         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
931         snprintf(command, MSG_SIZ, "%s %s", p, params);
932         p = command;
933     }
934     ASSIGN(appData.chessProgram[i], p);
935     appData.isUCI[i] = isUCI;
936     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
937     appData.hasOwnBookUCI[i] = hasBook;
938     if(!nickName[0]) useNick = FALSE;
939     if(useNick) ASSIGN(appData.pgnName[i], nickName);
940     if(addToList) {
941         int len;
942         char quote;
943         q = firstChessProgramNames;
944         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
945         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
946         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
947                         quote, p, quote, appData.directory[i], 
948                         useNick ? " -fn \"" : "",
949                         useNick ? nickName : "",
950                         useNick ? "\"" : "",
951                         v1 ? " -firstProtocolVersion 1" : "",
952                         hasBook ? "" : " -fNoOwnBookUCI",
953                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
954                         storeVariant ? " -variant " : "",
955                         storeVariant ? VariantName(gameInfo.variant) : "");
956         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
957         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
958         if(insert != q) insert[-1] = NULLCHAR;
959         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
960         if(q)   free(q);
961         FloatToFront(&appData.recentEngineList, buf);
962     }
963     ReplaceEngine(cps, i);
964 }
965
966 void
967 InitTimeControls ()
968 {
969     int matched, min, sec;
970     /*
971      * Parse timeControl resource
972      */
973     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
974                           appData.movesPerSession)) {
975         char buf[MSG_SIZ];
976         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
977         DisplayFatalError(buf, 0, 2);
978     }
979
980     /*
981      * Parse searchTime resource
982      */
983     if (*appData.searchTime != NULLCHAR) {
984         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
985         if (matched == 1) {
986             searchTime = min * 60;
987         } else if (matched == 2) {
988             searchTime = min * 60 + sec;
989         } else {
990             char buf[MSG_SIZ];
991             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
992             DisplayFatalError(buf, 0, 2);
993         }
994     }
995 }
996
997 void
998 InitBackEnd1 ()
999 {
1000
1001     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1002     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1003
1004     GetTimeMark(&programStartTime);
1005     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1006     appData.seedBase = random() + (random()<<15);
1007     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1008
1009     ClearProgramStats();
1010     programStats.ok_to_send = 1;
1011     programStats.seen_stat = 0;
1012
1013     /*
1014      * Initialize game list
1015      */
1016     ListNew(&gameList);
1017
1018
1019     /*
1020      * Internet chess server status
1021      */
1022     if (appData.icsActive) {
1023         appData.matchMode = FALSE;
1024         appData.matchGames = 0;
1025 #if ZIPPY
1026         appData.noChessProgram = !appData.zippyPlay;
1027 #else
1028         appData.zippyPlay = FALSE;
1029         appData.zippyTalk = FALSE;
1030         appData.noChessProgram = TRUE;
1031 #endif
1032         if (*appData.icsHelper != NULLCHAR) {
1033             appData.useTelnet = TRUE;
1034             appData.telnetProgram = appData.icsHelper;
1035         }
1036     } else {
1037         appData.zippyTalk = appData.zippyPlay = FALSE;
1038     }
1039
1040     /* [AS] Initialize pv info list [HGM] and game state */
1041     {
1042         int i, j;
1043
1044         for( i=0; i<=framePtr; i++ ) {
1045             pvInfoList[i].depth = -1;
1046             boards[i][EP_STATUS] = EP_NONE;
1047             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1048         }
1049     }
1050
1051     InitTimeControls();
1052
1053     /* [AS] Adjudication threshold */
1054     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1055
1056     InitEngine(&first, 0);
1057     InitEngine(&second, 1);
1058     CommonEngineInit();
1059
1060     pairing.which = "pairing"; // pairing engine
1061     pairing.pr = NoProc;
1062     pairing.isr = NULL;
1063     pairing.program = appData.pairingEngine;
1064     pairing.host = "localhost";
1065     pairing.dir = ".";
1066
1067     if (appData.icsActive) {
1068         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1069     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1070         appData.clockMode = FALSE;
1071         first.sendTime = second.sendTime = 0;
1072     }
1073
1074 #if ZIPPY
1075     /* Override some settings from environment variables, for backward
1076        compatibility.  Unfortunately it's not feasible to have the env
1077        vars just set defaults, at least in xboard.  Ugh.
1078     */
1079     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1080       ZippyInit();
1081     }
1082 #endif
1083
1084     if (!appData.icsActive) {
1085       char buf[MSG_SIZ];
1086       int len;
1087
1088       /* Check for variants that are supported only in ICS mode,
1089          or not at all.  Some that are accepted here nevertheless
1090          have bugs; see comments below.
1091       */
1092       VariantClass variant = StringToVariant(appData.variant);
1093       switch (variant) {
1094       case VariantBughouse:     /* need four players and two boards */
1095       case VariantKriegspiel:   /* need to hide pieces and move details */
1096         /* case VariantFischeRandom: (Fabien: moved below) */
1097         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1098         if( (len >= MSG_SIZ) && appData.debugMode )
1099           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1100
1101         DisplayFatalError(buf, 0, 2);
1102         return;
1103
1104       case VariantUnknown:
1105       case VariantLoadable:
1106       case Variant29:
1107       case Variant30:
1108       case Variant31:
1109       case Variant32:
1110       case Variant33:
1111       case Variant34:
1112       case Variant35:
1113       case Variant36:
1114       default:
1115         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1116         if( (len >= MSG_SIZ) && appData.debugMode )
1117           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1118
1119         DisplayFatalError(buf, 0, 2);
1120         return;
1121
1122       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1123       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1124       case VariantGothic:     /* [HGM] should work */
1125       case VariantCapablanca: /* [HGM] should work */
1126       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1127       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1128       case VariantKnightmate: /* [HGM] should work */
1129       case VariantCylinder:   /* [HGM] untested */
1130       case VariantFalcon:     /* [HGM] untested */
1131       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1132                                  offboard interposition not understood */
1133       case VariantNormal:     /* definitely works! */
1134       case VariantWildCastle: /* pieces not automatically shuffled */
1135       case VariantNoCastle:   /* pieces not automatically shuffled */
1136       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1137       case VariantLosers:     /* should work except for win condition,
1138                                  and doesn't know captures are mandatory */
1139       case VariantSuicide:    /* should work except for win condition,
1140                                  and doesn't know captures are mandatory */
1141       case VariantGiveaway:   /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantTwoKings:   /* should work */
1144       case VariantAtomic:     /* should work except for win condition */
1145       case Variant3Check:     /* should work except for win condition */
1146       case VariantShatranj:   /* should work except for all win conditions */
1147       case VariantMakruk:     /* should work except for draw countdown */
1148       case VariantBerolina:   /* might work if TestLegality is off */
1149       case VariantCapaRandom: /* should work */
1150       case VariantJanus:      /* should work */
1151       case VariantSuper:      /* experimental */
1152       case VariantGreat:      /* experimental, requires legality testing to be off */
1153       case VariantSChess:     /* S-Chess, should work */
1154       case VariantGrand:      /* should work */
1155       case VariantSpartan:    /* should work */
1156         break;
1157       }
1158     }
1159
1160 }
1161
1162 int
1163 NextIntegerFromString (char ** str, long * value)
1164 {
1165     int result = -1;
1166     char * s = *str;
1167
1168     while( *s == ' ' || *s == '\t' ) {
1169         s++;
1170     }
1171
1172     *value = 0;
1173
1174     if( *s >= '0' && *s <= '9' ) {
1175         while( *s >= '0' && *s <= '9' ) {
1176             *value = *value * 10 + (*s - '0');
1177             s++;
1178         }
1179
1180         result = 0;
1181     }
1182
1183     *str = s;
1184
1185     return result;
1186 }
1187
1188 int
1189 NextTimeControlFromString (char ** str, long * value)
1190 {
1191     long temp;
1192     int result = NextIntegerFromString( str, &temp );
1193
1194     if( result == 0 ) {
1195         *value = temp * 60; /* Minutes */
1196         if( **str == ':' ) {
1197             (*str)++;
1198             result = NextIntegerFromString( str, &temp );
1199             *value += temp; /* Seconds */
1200         }
1201     }
1202
1203     return result;
1204 }
1205
1206 int
1207 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1208 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1209     int result = -1, type = 0; long temp, temp2;
1210
1211     if(**str != ':') return -1; // old params remain in force!
1212     (*str)++;
1213     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1214     if( NextIntegerFromString( str, &temp ) ) return -1;
1215     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1216
1217     if(**str != '/') {
1218         /* time only: incremental or sudden-death time control */
1219         if(**str == '+') { /* increment follows; read it */
1220             (*str)++;
1221             if(**str == '!') type = *(*str)++; // Bronstein TC
1222             if(result = NextIntegerFromString( str, &temp2)) return -1;
1223             *inc = temp2 * 1000;
1224             if(**str == '.') { // read fraction of increment
1225                 char *start = ++(*str);
1226                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1227                 temp2 *= 1000;
1228                 while(start++ < *str) temp2 /= 10;
1229                 *inc += temp2;
1230             }
1231         } else *inc = 0;
1232         *moves = 0; *tc = temp * 1000; *incType = type;
1233         return 0;
1234     }
1235
1236     (*str)++; /* classical time control */
1237     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1238
1239     if(result == 0) {
1240         *moves = temp;
1241         *tc    = temp2 * 1000;
1242         *inc   = 0;
1243         *incType = type;
1244     }
1245     return result;
1246 }
1247
1248 int
1249 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1250 {   /* [HGM] get time to add from the multi-session time-control string */
1251     int incType, moves=1; /* kludge to force reading of first session */
1252     long time, increment;
1253     char *s = tcString;
1254
1255     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1256     do {
1257         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1258         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1259         if(movenr == -1) return time;    /* last move before new session     */
1260         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1261         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1262         if(!moves) return increment;     /* current session is incremental   */
1263         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1264     } while(movenr >= -1);               /* try again for next session       */
1265
1266     return 0; // no new time quota on this move
1267 }
1268
1269 int
1270 ParseTimeControl (char *tc, float ti, int mps)
1271 {
1272   long tc1;
1273   long tc2;
1274   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1275   int min, sec=0;
1276
1277   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1278   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1279       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1280   if(ti > 0) {
1281
1282     if(mps)
1283       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1284     else 
1285       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1286   } else {
1287     if(mps)
1288       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1289     else 
1290       snprintf(buf, MSG_SIZ, ":%s", mytc);
1291   }
1292   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1293   
1294   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1295     return FALSE;
1296   }
1297
1298   if( *tc == '/' ) {
1299     /* Parse second time control */
1300     tc++;
1301
1302     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1303       return FALSE;
1304     }
1305
1306     if( tc2 == 0 ) {
1307       return FALSE;
1308     }
1309
1310     timeControl_2 = tc2 * 1000;
1311   }
1312   else {
1313     timeControl_2 = 0;
1314   }
1315
1316   if( tc1 == 0 ) {
1317     return FALSE;
1318   }
1319
1320   timeControl = tc1 * 1000;
1321
1322   if (ti >= 0) {
1323     timeIncrement = ti * 1000;  /* convert to ms */
1324     movesPerSession = 0;
1325   } else {
1326     timeIncrement = 0;
1327     movesPerSession = mps;
1328   }
1329   return TRUE;
1330 }
1331
1332 void
1333 InitBackEnd2 ()
1334 {
1335     if (appData.debugMode) {
1336         fprintf(debugFP, "%s\n", programVersion);
1337     }
1338     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1339
1340     set_cont_sequence(appData.wrapContSeq);
1341     if (appData.matchGames > 0) {
1342         appData.matchMode = TRUE;
1343     } else if (appData.matchMode) {
1344         appData.matchGames = 1;
1345     }
1346     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1347         appData.matchGames = appData.sameColorGames;
1348     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1349         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1350         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1351     }
1352     Reset(TRUE, FALSE);
1353     if (appData.noChessProgram || first.protocolVersion == 1) {
1354       InitBackEnd3();
1355     } else {
1356       /* kludge: allow timeout for initial "feature" commands */
1357       FreezeUI();
1358       DisplayMessage("", _("Starting chess program"));
1359       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1360     }
1361 }
1362
1363 int
1364 CalculateIndex (int index, int gameNr)
1365 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1366     int res;
1367     if(index > 0) return index; // fixed nmber
1368     if(index == 0) return 1;
1369     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1370     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1371     return res;
1372 }
1373
1374 int
1375 LoadGameOrPosition (int gameNr)
1376 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1377     if (*appData.loadGameFile != NULLCHAR) {
1378         if (!LoadGameFromFile(appData.loadGameFile,
1379                 CalculateIndex(appData.loadGameIndex, gameNr),
1380                               appData.loadGameFile, FALSE)) {
1381             DisplayFatalError(_("Bad game file"), 0, 1);
1382             return 0;
1383         }
1384     } else if (*appData.loadPositionFile != NULLCHAR) {
1385         if (!LoadPositionFromFile(appData.loadPositionFile,
1386                 CalculateIndex(appData.loadPositionIndex, gameNr),
1387                                   appData.loadPositionFile)) {
1388             DisplayFatalError(_("Bad position file"), 0, 1);
1389             return 0;
1390         }
1391     }
1392     return 1;
1393 }
1394
1395 void
1396 ReserveGame (int gameNr, char resChar)
1397 {
1398     FILE *tf = fopen(appData.tourneyFile, "r+");
1399     char *p, *q, c, buf[MSG_SIZ];
1400     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1401     safeStrCpy(buf, lastMsg, MSG_SIZ);
1402     DisplayMessage(_("Pick new game"), "");
1403     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1404     ParseArgsFromFile(tf);
1405     p = q = appData.results;
1406     if(appData.debugMode) {
1407       char *r = appData.participants;
1408       fprintf(debugFP, "results = '%s'\n", p);
1409       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1410       fprintf(debugFP, "\n");
1411     }
1412     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1413     nextGame = q - p;
1414     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1415     safeStrCpy(q, p, strlen(p) + 2);
1416     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1417     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1418     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1419         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1420         q[nextGame] = '*';
1421     }
1422     fseek(tf, -(strlen(p)+4), SEEK_END);
1423     c = fgetc(tf);
1424     if(c != '"') // depending on DOS or Unix line endings we can be one off
1425          fseek(tf, -(strlen(p)+2), SEEK_END);
1426     else fseek(tf, -(strlen(p)+3), SEEK_END);
1427     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1428     DisplayMessage(buf, "");
1429     free(p); appData.results = q;
1430     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1431        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1432       int round = appData.defaultMatchGames * appData.tourneyType;
1433       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1434          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1435         UnloadEngine(&first);  // next game belongs to other pairing;
1436         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1437     }
1438     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1439 }
1440
1441 void
1442 MatchEvent (int mode)
1443 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1444         int dummy;
1445         if(matchMode) { // already in match mode: switch it off
1446             abortMatch = TRUE;
1447             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1448             return;
1449         }
1450 //      if(gameMode != BeginningOfGame) {
1451 //          DisplayError(_("You can only start a match from the initial position."), 0);
1452 //          return;
1453 //      }
1454         abortMatch = FALSE;
1455         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1456         /* Set up machine vs. machine match */
1457         nextGame = 0;
1458         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1459         if(appData.tourneyFile[0]) {
1460             ReserveGame(-1, 0);
1461             if(nextGame > appData.matchGames) {
1462                 char buf[MSG_SIZ];
1463                 if(strchr(appData.results, '*') == NULL) {
1464                     FILE *f;
1465                     appData.tourneyCycles++;
1466                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1467                         fclose(f);
1468                         NextTourneyGame(-1, &dummy);
1469                         ReserveGame(-1, 0);
1470                         if(nextGame <= appData.matchGames) {
1471                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1472                             matchMode = mode;
1473                             ScheduleDelayedEvent(NextMatchGame, 10000);
1474                             return;
1475                         }
1476                     }
1477                 }
1478                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1479                 DisplayError(buf, 0);
1480                 appData.tourneyFile[0] = 0;
1481                 return;
1482             }
1483         } else
1484         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1485             DisplayFatalError(_("Can't have a match with no chess programs"),
1486                               0, 2);
1487             return;
1488         }
1489         matchMode = mode;
1490         matchGame = roundNr = 1;
1491         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1492         NextMatchGame();
1493 }
1494
1495 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1496
1497 void
1498 InitBackEnd3 P((void))
1499 {
1500     GameMode initialMode;
1501     char buf[MSG_SIZ];
1502     int err, len;
1503
1504     InitChessProgram(&first, startedFromSetupPosition);
1505
1506     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1507         free(programVersion);
1508         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1509         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1510         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1511     }
1512
1513     if (appData.icsActive) {
1514 #ifdef WIN32
1515         /* [DM] Make a console window if needed [HGM] merged ifs */
1516         ConsoleCreate();
1517 #endif
1518         err = establish();
1519         if (err != 0)
1520           {
1521             if (*appData.icsCommPort != NULLCHAR)
1522               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1523                              appData.icsCommPort);
1524             else
1525               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1526                         appData.icsHost, appData.icsPort);
1527
1528             if( (len >= MSG_SIZ) && appData.debugMode )
1529               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1530
1531             DisplayFatalError(buf, err, 1);
1532             return;
1533         }
1534         SetICSMode();
1535         telnetISR =
1536           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1537         fromUserISR =
1538           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1539         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1540             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1541     } else if (appData.noChessProgram) {
1542         SetNCPMode();
1543     } else {
1544         SetGNUMode();
1545     }
1546
1547     if (*appData.cmailGameName != NULLCHAR) {
1548         SetCmailMode();
1549         OpenLoopback(&cmailPR);
1550         cmailISR =
1551           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1552     }
1553
1554     ThawUI();
1555     DisplayMessage("", "");
1556     if (StrCaseCmp(appData.initialMode, "") == 0) {
1557       initialMode = BeginningOfGame;
1558       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1559         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1560         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1561         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1562         ModeHighlight();
1563       }
1564     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1565       initialMode = TwoMachinesPlay;
1566     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1567       initialMode = AnalyzeFile;
1568     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1569       initialMode = AnalyzeMode;
1570     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1571       initialMode = MachinePlaysWhite;
1572     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1573       initialMode = MachinePlaysBlack;
1574     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1575       initialMode = EditGame;
1576     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1577       initialMode = EditPosition;
1578     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1579       initialMode = Training;
1580     } else {
1581       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1582       if( (len >= MSG_SIZ) && appData.debugMode )
1583         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585       DisplayFatalError(buf, 0, 2);
1586       return;
1587     }
1588
1589     if (appData.matchMode) {
1590         if(appData.tourneyFile[0]) { // start tourney from command line
1591             FILE *f;
1592             if(f = fopen(appData.tourneyFile, "r")) {
1593                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1594                 fclose(f);
1595                 appData.clockMode = TRUE;
1596                 SetGNUMode();
1597             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1598         }
1599         MatchEvent(TRUE);
1600     } else if (*appData.cmailGameName != NULLCHAR) {
1601         /* Set up cmail mode */
1602         ReloadCmailMsgEvent(TRUE);
1603     } else {
1604         /* Set up other modes */
1605         if (initialMode == AnalyzeFile) {
1606           if (*appData.loadGameFile == NULLCHAR) {
1607             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1608             return;
1609           }
1610         }
1611         if (*appData.loadGameFile != NULLCHAR) {
1612             (void) LoadGameFromFile(appData.loadGameFile,
1613                                     appData.loadGameIndex,
1614                                     appData.loadGameFile, TRUE);
1615         } else if (*appData.loadPositionFile != NULLCHAR) {
1616             (void) LoadPositionFromFile(appData.loadPositionFile,
1617                                         appData.loadPositionIndex,
1618                                         appData.loadPositionFile);
1619             /* [HGM] try to make self-starting even after FEN load */
1620             /* to allow automatic setup of fairy variants with wtm */
1621             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1622                 gameMode = BeginningOfGame;
1623                 setboardSpoiledMachineBlack = 1;
1624             }
1625             /* [HGM] loadPos: make that every new game uses the setup */
1626             /* from file as long as we do not switch variant          */
1627             if(!blackPlaysFirst) {
1628                 startedFromPositionFile = TRUE;
1629                 CopyBoard(filePosition, boards[0]);
1630             }
1631         }
1632         if (initialMode == AnalyzeMode) {
1633           if (appData.noChessProgram) {
1634             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1639             return;
1640           }
1641           AnalyzeModeEvent();
1642         } else if (initialMode == AnalyzeFile) {
1643           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1644           ShowThinkingEvent();
1645           AnalyzeFileEvent();
1646           AnalysisPeriodicEvent(1);
1647         } else if (initialMode == MachinePlaysWhite) {
1648           if (appData.noChessProgram) {
1649             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1650                               0, 2);
1651             return;
1652           }
1653           if (appData.icsActive) {
1654             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1655                               0, 2);
1656             return;
1657           }
1658           MachineWhiteEvent();
1659         } else if (initialMode == MachinePlaysBlack) {
1660           if (appData.noChessProgram) {
1661             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1662                               0, 2);
1663             return;
1664           }
1665           if (appData.icsActive) {
1666             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1667                               0, 2);
1668             return;
1669           }
1670           MachineBlackEvent();
1671         } else if (initialMode == TwoMachinesPlay) {
1672           if (appData.noChessProgram) {
1673             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1674                               0, 2);
1675             return;
1676           }
1677           if (appData.icsActive) {
1678             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1679                               0, 2);
1680             return;
1681           }
1682           TwoMachinesEvent();
1683         } else if (initialMode == EditGame) {
1684           EditGameEvent();
1685         } else if (initialMode == EditPosition) {
1686           EditPositionEvent();
1687         } else if (initialMode == Training) {
1688           if (*appData.loadGameFile == NULLCHAR) {
1689             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1690             return;
1691           }
1692           TrainingEvent();
1693         }
1694     }
1695 }
1696
1697 void
1698 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1699 {
1700     DisplayBook(current+1);
1701
1702     MoveHistorySet( movelist, first, last, current, pvInfoList );
1703
1704     EvalGraphSet( first, last, current, pvInfoList );
1705
1706     MakeEngineOutputTitle();
1707 }
1708
1709 /*
1710  * Establish will establish a contact to a remote host.port.
1711  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1712  *  used to talk to the host.
1713  * Returns 0 if okay, error code if not.
1714  */
1715 int
1716 establish ()
1717 {
1718     char buf[MSG_SIZ];
1719
1720     if (*appData.icsCommPort != NULLCHAR) {
1721         /* Talk to the host through a serial comm port */
1722         return OpenCommPort(appData.icsCommPort, &icsPR);
1723
1724     } else if (*appData.gateway != NULLCHAR) {
1725         if (*appData.remoteShell == NULLCHAR) {
1726             /* Use the rcmd protocol to run telnet program on a gateway host */
1727             snprintf(buf, sizeof(buf), "%s %s %s",
1728                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1729             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1730
1731         } else {
1732             /* Use the rsh program to run telnet program on a gateway host */
1733             if (*appData.remoteUser == NULLCHAR) {
1734                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1735                         appData.gateway, appData.telnetProgram,
1736                         appData.icsHost, appData.icsPort);
1737             } else {
1738                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1739                         appData.remoteShell, appData.gateway,
1740                         appData.remoteUser, appData.telnetProgram,
1741                         appData.icsHost, appData.icsPort);
1742             }
1743             return StartChildProcess(buf, "", &icsPR);
1744
1745         }
1746     } else if (appData.useTelnet) {
1747         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1748
1749     } else {
1750         /* TCP socket interface differs somewhat between
1751            Unix and NT; handle details in the front end.
1752            */
1753         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1754     }
1755 }
1756
1757 void
1758 EscapeExpand (char *p, char *q)
1759 {       // [HGM] initstring: routine to shape up string arguments
1760         while(*p++ = *q++) if(p[-1] == '\\')
1761             switch(*q++) {
1762                 case 'n': p[-1] = '\n'; break;
1763                 case 'r': p[-1] = '\r'; break;
1764                 case 't': p[-1] = '\t'; break;
1765                 case '\\': p[-1] = '\\'; break;
1766                 case 0: *p = 0; return;
1767                 default: p[-1] = q[-1]; break;
1768             }
1769 }
1770
1771 void
1772 show_bytes (FILE *fp, char *buf, int count)
1773 {
1774     while (count--) {
1775         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1776             fprintf(fp, "\\%03o", *buf & 0xff);
1777         } else {
1778             putc(*buf, fp);
1779         }
1780         buf++;
1781     }
1782     fflush(fp);
1783 }
1784
1785 /* Returns an errno value */
1786 int
1787 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1788 {
1789     char buf[8192], *p, *q, *buflim;
1790     int left, newcount, outcount;
1791
1792     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1793         *appData.gateway != NULLCHAR) {
1794         if (appData.debugMode) {
1795             fprintf(debugFP, ">ICS: ");
1796             show_bytes(debugFP, message, count);
1797             fprintf(debugFP, "\n");
1798         }
1799         return OutputToProcess(pr, message, count, outError);
1800     }
1801
1802     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1803     p = message;
1804     q = buf;
1805     left = count;
1806     newcount = 0;
1807     while (left) {
1808         if (q >= buflim) {
1809             if (appData.debugMode) {
1810                 fprintf(debugFP, ">ICS: ");
1811                 show_bytes(debugFP, buf, newcount);
1812                 fprintf(debugFP, "\n");
1813             }
1814             outcount = OutputToProcess(pr, buf, newcount, outError);
1815             if (outcount < newcount) return -1; /* to be sure */
1816             q = buf;
1817             newcount = 0;
1818         }
1819         if (*p == '\n') {
1820             *q++ = '\r';
1821             newcount++;
1822         } else if (((unsigned char) *p) == TN_IAC) {
1823             *q++ = (char) TN_IAC;
1824             newcount ++;
1825         }
1826         *q++ = *p++;
1827         newcount++;
1828         left--;
1829     }
1830     if (appData.debugMode) {
1831         fprintf(debugFP, ">ICS: ");
1832         show_bytes(debugFP, buf, newcount);
1833         fprintf(debugFP, "\n");
1834     }
1835     outcount = OutputToProcess(pr, buf, newcount, outError);
1836     if (outcount < newcount) return -1; /* to be sure */
1837     return count;
1838 }
1839
1840 void
1841 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1842 {
1843     int outError, outCount;
1844     static int gotEof = 0;
1845
1846     /* Pass data read from player on to ICS */
1847     if (count > 0) {
1848         gotEof = 0;
1849         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1850         if (outCount < count) {
1851             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1852         }
1853     } else if (count < 0) {
1854         RemoveInputSource(isr);
1855         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1856     } else if (gotEof++ > 0) {
1857         RemoveInputSource(isr);
1858         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1859     }
1860 }
1861
1862 void
1863 KeepAlive ()
1864 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1865     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1866     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1867     SendToICS("date\n");
1868     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1869 }
1870
1871 /* added routine for printf style output to ics */
1872 void
1873 ics_printf (char *format, ...)
1874 {
1875     char buffer[MSG_SIZ];
1876     va_list args;
1877
1878     va_start(args, format);
1879     vsnprintf(buffer, sizeof(buffer), format, args);
1880     buffer[sizeof(buffer)-1] = '\0';
1881     SendToICS(buffer);
1882     va_end(args);
1883 }
1884
1885 void
1886 SendToICS (char *s)
1887 {
1888     int count, outCount, outError;
1889
1890     if (icsPR == NoProc) return;
1891
1892     count = strlen(s);
1893     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1894     if (outCount < count) {
1895         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896     }
1897 }
1898
1899 /* This is used for sending logon scripts to the ICS. Sending
1900    without a delay causes problems when using timestamp on ICC
1901    (at least on my machine). */
1902 void
1903 SendToICSDelayed (char *s, long msdelay)
1904 {
1905     int count, outCount, outError;
1906
1907     if (icsPR == NoProc) return;
1908
1909     count = strlen(s);
1910     if (appData.debugMode) {
1911         fprintf(debugFP, ">ICS: ");
1912         show_bytes(debugFP, s, count);
1913         fprintf(debugFP, "\n");
1914     }
1915     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1916                                       msdelay);
1917     if (outCount < count) {
1918         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1919     }
1920 }
1921
1922
1923 /* Remove all highlighting escape sequences in s
1924    Also deletes any suffix starting with '('
1925    */
1926 char *
1927 StripHighlightAndTitle (char *s)
1928 {
1929     static char retbuf[MSG_SIZ];
1930     char *p = retbuf;
1931
1932     while (*s != NULLCHAR) {
1933         while (*s == '\033') {
1934             while (*s != NULLCHAR && !isalpha(*s)) s++;
1935             if (*s != NULLCHAR) s++;
1936         }
1937         while (*s != NULLCHAR && *s != '\033') {
1938             if (*s == '(' || *s == '[') {
1939                 *p = NULLCHAR;
1940                 return retbuf;
1941             }
1942             *p++ = *s++;
1943         }
1944     }
1945     *p = NULLCHAR;
1946     return retbuf;
1947 }
1948
1949 /* Remove all highlighting escape sequences in s */
1950 char *
1951 StripHighlight (char *s)
1952 {
1953     static char retbuf[MSG_SIZ];
1954     char *p = retbuf;
1955
1956     while (*s != NULLCHAR) {
1957         while (*s == '\033') {
1958             while (*s != NULLCHAR && !isalpha(*s)) s++;
1959             if (*s != NULLCHAR) s++;
1960         }
1961         while (*s != NULLCHAR && *s != '\033') {
1962             *p++ = *s++;
1963         }
1964     }
1965     *p = NULLCHAR;
1966     return retbuf;
1967 }
1968
1969 char *variantNames[] = VARIANT_NAMES;
1970 char *
1971 VariantName (VariantClass v)
1972 {
1973     return variantNames[v];
1974 }
1975
1976
1977 /* Identify a variant from the strings the chess servers use or the
1978    PGN Variant tag names we use. */
1979 VariantClass
1980 StringToVariant (char *e)
1981 {
1982     char *p;
1983     int wnum = -1;
1984     VariantClass v = VariantNormal;
1985     int i, found = FALSE;
1986     char buf[MSG_SIZ];
1987     int len;
1988
1989     if (!e) return v;
1990
1991     /* [HGM] skip over optional board-size prefixes */
1992     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1993         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1994         while( *e++ != '_');
1995     }
1996
1997     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1998         v = VariantNormal;
1999         found = TRUE;
2000     } else
2001     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2002       if (StrCaseStr(e, variantNames[i])) {
2003         v = (VariantClass) i;
2004         found = TRUE;
2005         break;
2006       }
2007     }
2008
2009     if (!found) {
2010       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2011           || StrCaseStr(e, "wild/fr")
2012           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2013         v = VariantFischeRandom;
2014       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2015                  (i = 1, p = StrCaseStr(e, "w"))) {
2016         p += i;
2017         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2018         if (isdigit(*p)) {
2019           wnum = atoi(p);
2020         } else {
2021           wnum = -1;
2022         }
2023         switch (wnum) {
2024         case 0: /* FICS only, actually */
2025         case 1:
2026           /* Castling legal even if K starts on d-file */
2027           v = VariantWildCastle;
2028           break;
2029         case 2:
2030         case 3:
2031         case 4:
2032           /* Castling illegal even if K & R happen to start in
2033              normal positions. */
2034           v = VariantNoCastle;
2035           break;
2036         case 5:
2037         case 7:
2038         case 8:
2039         case 10:
2040         case 11:
2041         case 12:
2042         case 13:
2043         case 14:
2044         case 15:
2045         case 18:
2046         case 19:
2047           /* Castling legal iff K & R start in normal positions */
2048           v = VariantNormal;
2049           break;
2050         case 6:
2051         case 20:
2052         case 21:
2053           /* Special wilds for position setup; unclear what to do here */
2054           v = VariantLoadable;
2055           break;
2056         case 9:
2057           /* Bizarre ICC game */
2058           v = VariantTwoKings;
2059           break;
2060         case 16:
2061           v = VariantKriegspiel;
2062           break;
2063         case 17:
2064           v = VariantLosers;
2065           break;
2066         case 22:
2067           v = VariantFischeRandom;
2068           break;
2069         case 23:
2070           v = VariantCrazyhouse;
2071           break;
2072         case 24:
2073           v = VariantBughouse;
2074           break;
2075         case 25:
2076           v = Variant3Check;
2077           break;
2078         case 26:
2079           /* Not quite the same as FICS suicide! */
2080           v = VariantGiveaway;
2081           break;
2082         case 27:
2083           v = VariantAtomic;
2084           break;
2085         case 28:
2086           v = VariantShatranj;
2087           break;
2088
2089         /* Temporary names for future ICC types.  The name *will* change in
2090            the next xboard/WinBoard release after ICC defines it. */
2091         case 29:
2092           v = Variant29;
2093           break;
2094         case 30:
2095           v = Variant30;
2096           break;
2097         case 31:
2098           v = Variant31;
2099           break;
2100         case 32:
2101           v = Variant32;
2102           break;
2103         case 33:
2104           v = Variant33;
2105           break;
2106         case 34:
2107           v = Variant34;
2108           break;
2109         case 35:
2110           v = Variant35;
2111           break;
2112         case 36:
2113           v = Variant36;
2114           break;
2115         case 37:
2116           v = VariantShogi;
2117           break;
2118         case 38:
2119           v = VariantXiangqi;
2120           break;
2121         case 39:
2122           v = VariantCourier;
2123           break;
2124         case 40:
2125           v = VariantGothic;
2126           break;
2127         case 41:
2128           v = VariantCapablanca;
2129           break;
2130         case 42:
2131           v = VariantKnightmate;
2132           break;
2133         case 43:
2134           v = VariantFairy;
2135           break;
2136         case 44:
2137           v = VariantCylinder;
2138           break;
2139         case 45:
2140           v = VariantFalcon;
2141           break;
2142         case 46:
2143           v = VariantCapaRandom;
2144           break;
2145         case 47:
2146           v = VariantBerolina;
2147           break;
2148         case 48:
2149           v = VariantJanus;
2150           break;
2151         case 49:
2152           v = VariantSuper;
2153           break;
2154         case 50:
2155           v = VariantGreat;
2156           break;
2157         case -1:
2158           /* Found "wild" or "w" in the string but no number;
2159              must assume it's normal chess. */
2160           v = VariantNormal;
2161           break;
2162         default:
2163           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2164           if( (len >= MSG_SIZ) && appData.debugMode )
2165             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2166
2167           DisplayError(buf, 0);
2168           v = VariantUnknown;
2169           break;
2170         }
2171       }
2172     }
2173     if (appData.debugMode) {
2174       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2175               e, wnum, VariantName(v));
2176     }
2177     return v;
2178 }
2179
2180 static int leftover_start = 0, leftover_len = 0;
2181 char star_match[STAR_MATCH_N][MSG_SIZ];
2182
2183 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2184    advance *index beyond it, and set leftover_start to the new value of
2185    *index; else return FALSE.  If pattern contains the character '*', it
2186    matches any sequence of characters not containing '\r', '\n', or the
2187    character following the '*' (if any), and the matched sequence(s) are
2188    copied into star_match.
2189    */
2190 int
2191 looking_at ( char *buf, int *index, char *pattern)
2192 {
2193     char *bufp = &buf[*index], *patternp = pattern;
2194     int star_count = 0;
2195     char *matchp = star_match[0];
2196
2197     for (;;) {
2198         if (*patternp == NULLCHAR) {
2199             *index = leftover_start = bufp - buf;
2200             *matchp = NULLCHAR;
2201             return TRUE;
2202         }
2203         if (*bufp == NULLCHAR) return FALSE;
2204         if (*patternp == '*') {
2205             if (*bufp == *(patternp + 1)) {
2206                 *matchp = NULLCHAR;
2207                 matchp = star_match[++star_count];
2208                 patternp += 2;
2209                 bufp++;
2210                 continue;
2211             } else if (*bufp == '\n' || *bufp == '\r') {
2212                 patternp++;
2213                 if (*patternp == NULLCHAR)
2214                   continue;
2215                 else
2216                   return FALSE;
2217             } else {
2218                 *matchp++ = *bufp++;
2219                 continue;
2220             }
2221         }
2222         if (*patternp != *bufp) return FALSE;
2223         patternp++;
2224         bufp++;
2225     }
2226 }
2227
2228 void
2229 SendToPlayer (char *data, int length)
2230 {
2231     int error, outCount;
2232     outCount = OutputToProcess(NoProc, data, length, &error);
2233     if (outCount < length) {
2234         DisplayFatalError(_("Error writing to display"), error, 1);
2235     }
2236 }
2237
2238 void
2239 PackHolding (char packed[], char *holding)
2240 {
2241     char *p = holding;
2242     char *q = packed;
2243     int runlength = 0;
2244     int curr = 9999;
2245     do {
2246         if (*p == curr) {
2247             runlength++;
2248         } else {
2249             switch (runlength) {
2250               case 0:
2251                 break;
2252               case 1:
2253                 *q++ = curr;
2254                 break;
2255               case 2:
2256                 *q++ = curr;
2257                 *q++ = curr;
2258                 break;
2259               default:
2260                 sprintf(q, "%d", runlength);
2261                 while (*q) q++;
2262                 *q++ = curr;
2263                 break;
2264             }
2265             runlength = 1;
2266             curr = *p;
2267         }
2268     } while (*p++);
2269     *q = NULLCHAR;
2270 }
2271
2272 /* Telnet protocol requests from the front end */
2273 void
2274 TelnetRequest (unsigned char ddww, unsigned char option)
2275 {
2276     unsigned char msg[3];
2277     int outCount, outError;
2278
2279     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2280
2281     if (appData.debugMode) {
2282         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2283         switch (ddww) {
2284           case TN_DO:
2285             ddwwStr = "DO";
2286             break;
2287           case TN_DONT:
2288             ddwwStr = "DONT";
2289             break;
2290           case TN_WILL:
2291             ddwwStr = "WILL";
2292             break;
2293           case TN_WONT:
2294             ddwwStr = "WONT";
2295             break;
2296           default:
2297             ddwwStr = buf1;
2298             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2299             break;
2300         }
2301         switch (option) {
2302           case TN_ECHO:
2303             optionStr = "ECHO";
2304             break;
2305           default:
2306             optionStr = buf2;
2307             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2308             break;
2309         }
2310         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2311     }
2312     msg[0] = TN_IAC;
2313     msg[1] = ddww;
2314     msg[2] = option;
2315     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2316     if (outCount < 3) {
2317         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2318     }
2319 }
2320
2321 void
2322 DoEcho ()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DO, TN_ECHO);
2326 }
2327
2328 void
2329 DontEcho ()
2330 {
2331     if (!appData.icsActive) return;
2332     TelnetRequest(TN_DONT, TN_ECHO);
2333 }
2334
2335 void
2336 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2337 {
2338     /* put the holdings sent to us by the server on the board holdings area */
2339     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2340     char p;
2341     ChessSquare piece;
2342
2343     if(gameInfo.holdingsWidth < 2)  return;
2344     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2345         return; // prevent overwriting by pre-board holdings
2346
2347     if( (int)lowestPiece >= BlackPawn ) {
2348         holdingsColumn = 0;
2349         countsColumn = 1;
2350         holdingsStartRow = BOARD_HEIGHT-1;
2351         direction = -1;
2352     } else {
2353         holdingsColumn = BOARD_WIDTH-1;
2354         countsColumn = BOARD_WIDTH-2;
2355         holdingsStartRow = 0;
2356         direction = 1;
2357     }
2358
2359     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2360         board[i][holdingsColumn] = EmptySquare;
2361         board[i][countsColumn]   = (ChessSquare) 0;
2362     }
2363     while( (p=*holdings++) != NULLCHAR ) {
2364         piece = CharToPiece( ToUpper(p) );
2365         if(piece == EmptySquare) continue;
2366         /*j = (int) piece - (int) WhitePawn;*/
2367         j = PieceToNumber(piece);
2368         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2369         if(j < 0) continue;               /* should not happen */
2370         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2371         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2372         board[holdingsStartRow+j*direction][countsColumn]++;
2373     }
2374 }
2375
2376
2377 void
2378 VariantSwitch (Board board, VariantClass newVariant)
2379 {
2380    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2381    static Board oldBoard;
2382
2383    startedFromPositionFile = FALSE;
2384    if(gameInfo.variant == newVariant) return;
2385
2386    /* [HGM] This routine is called each time an assignment is made to
2387     * gameInfo.variant during a game, to make sure the board sizes
2388     * are set to match the new variant. If that means adding or deleting
2389     * holdings, we shift the playing board accordingly
2390     * This kludge is needed because in ICS observe mode, we get boards
2391     * of an ongoing game without knowing the variant, and learn about the
2392     * latter only later. This can be because of the move list we requested,
2393     * in which case the game history is refilled from the beginning anyway,
2394     * but also when receiving holdings of a crazyhouse game. In the latter
2395     * case we want to add those holdings to the already received position.
2396     */
2397
2398
2399    if (appData.debugMode) {
2400      fprintf(debugFP, "Switch board from %s to %s\n",
2401              VariantName(gameInfo.variant), VariantName(newVariant));
2402      setbuf(debugFP, NULL);
2403    }
2404    shuffleOpenings = 0;       /* [HGM] shuffle */
2405    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2406    switch(newVariant)
2407      {
2408      case VariantShogi:
2409        newWidth = 9;  newHeight = 9;
2410        gameInfo.holdingsSize = 7;
2411      case VariantBughouse:
2412      case VariantCrazyhouse:
2413        newHoldingsWidth = 2; break;
2414      case VariantGreat:
2415        newWidth = 10;
2416      case VariantSuper:
2417        newHoldingsWidth = 2;
2418        gameInfo.holdingsSize = 8;
2419        break;
2420      case VariantGothic:
2421      case VariantCapablanca:
2422      case VariantCapaRandom:
2423        newWidth = 10;
2424      default:
2425        newHoldingsWidth = gameInfo.holdingsSize = 0;
2426      };
2427
2428    if(newWidth  != gameInfo.boardWidth  ||
2429       newHeight != gameInfo.boardHeight ||
2430       newHoldingsWidth != gameInfo.holdingsWidth ) {
2431
2432      /* shift position to new playing area, if needed */
2433      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438        for(i=0; i<newHeight; i++) {
2439          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2440          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2441        }
2442      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2443        for(i=0; i<BOARD_HEIGHT; i++)
2444          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2445            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2446              board[i][j];
2447      }
2448      board[HOLDINGS_SET] = 0;
2449      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.f) tc = 1.f;
2497         if(tc > 95.f) tc = 95.f;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <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
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #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"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4245         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4246          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4247       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4248       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4249       static int lastBgGame = -1;
4250       char *toSqr;
4251       for (k = 0; k < ranks; k++) {
4252         for (j = 0; j < files; j++)
4253           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4254         if(gameInfo.holdingsWidth > 1) {
4255              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4256              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4257         }
4258       }
4259       CopyBoard(partnerBoard, board);
4260       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4261         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4262         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4263       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4264       if(toSqr = strchr(str, '-')) {
4265         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4266         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4267       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4268       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4269       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4270       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271       if(twoBoards) {
4272           DisplayWhiteClock(white_time*fac, to_play == 'W');
4273           DisplayBlackClock(black_time*fac, to_play != 'W');
4274           activePartner = to_play;
4275           if(gamenum != lastBgGame) {
4276               char buf[MSG_SIZ];
4277               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4278               DisplayTitle(buf);
4279           }
4280           lastBgGame = gamenum;
4281           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4282                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4283       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4284                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4285       DisplayMessage(partnerStatus, "");
4286         partnerBoardValid = TRUE;
4287       return;
4288     }
4289
4290     if(appData.dualBoard && appData.bgObserve) {
4291         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4292             SendToICS(ics_prefix), SendToICS("pobserve\n");
4293         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4294             char buf[MSG_SIZ];
4295             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4296             SendToICS(buf);
4297         }
4298     }
4299
4300     /* Modify behavior for initial board display on move listing
4301        of wild games.
4302        */
4303     switch (ics_getting_history) {
4304       case H_FALSE:
4305       case H_REQUESTED:
4306         break;
4307       case H_GOT_REQ_HEADER:
4308       case H_GOT_UNREQ_HEADER:
4309         /* This is the initial position of the current game */
4310         gamenum = ics_gamenum;
4311         moveNum = 0;            /* old ICS bug workaround */
4312         if (to_play == 'B') {
4313           startedFromSetupPosition = TRUE;
4314           blackPlaysFirst = TRUE;
4315           moveNum = 1;
4316           if (forwardMostMove == 0) forwardMostMove = 1;
4317           if (backwardMostMove == 0) backwardMostMove = 1;
4318           if (currentMove == 0) currentMove = 1;
4319         }
4320         newGameMode = gameMode;
4321         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4322         break;
4323       case H_GOT_UNWANTED_HEADER:
4324         /* This is an initial board that we don't want */
4325         return;
4326       case H_GETTING_MOVES:
4327         /* Should not happen */
4328         DisplayError(_("Error gathering move list: extra board"), 0);
4329         ics_getting_history = H_FALSE;
4330         return;
4331     }
4332
4333    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4334                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4335                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4336      /* [HGM] We seem to have switched variant unexpectedly
4337       * Try to guess new variant from board size
4338       */
4339           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4340           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4341           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4342           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4343           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4344           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4345           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4346           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4347           /* Get a move list just to see the header, which
4348              will tell us whether this is really bug or zh */
4349           if (ics_getting_history == H_FALSE) {
4350             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4351             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352             SendToICS(str);
4353           }
4354     }
4355
4356     /* Take action if this is the first board of a new game, or of a
4357        different game than is currently being displayed.  */
4358     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4359         relation == RELATION_ISOLATED_BOARD) {
4360
4361         /* Forget the old game and get the history (if any) of the new one */
4362         if (gameMode != BeginningOfGame) {
4363           Reset(TRUE, TRUE);
4364         }
4365         newGame = TRUE;
4366         if (appData.autoRaiseBoard) BoardToTop();
4367         prevMove = -3;
4368         if (gamenum == -1) {
4369             newGameMode = IcsIdle;
4370         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4371                    appData.getMoveList && !reqFlag) {
4372             /* Need to get game history */
4373             ics_getting_history = H_REQUESTED;
4374             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4375             SendToICS(str);
4376         }
4377
4378         /* Initially flip the board to have black on the bottom if playing
4379            black or if the ICS flip flag is set, but let the user change
4380            it with the Flip View button. */
4381         flipView = appData.autoFlipView ?
4382           (newGameMode == IcsPlayingBlack) || ics_flip :
4383           appData.flipView;
4384
4385         /* Done with values from previous mode; copy in new ones */
4386         gameMode = newGameMode;
4387         ModeHighlight();
4388         ics_gamenum = gamenum;
4389         if (gamenum == gs_gamenum) {
4390             int klen = strlen(gs_kind);
4391             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4392             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4393             gameInfo.event = StrSave(str);
4394         } else {
4395             gameInfo.event = StrSave("ICS game");
4396         }
4397         gameInfo.site = StrSave(appData.icsHost);
4398         gameInfo.date = PGNDate();
4399         gameInfo.round = StrSave("-");
4400         gameInfo.white = StrSave(white);
4401         gameInfo.black = StrSave(black);
4402         timeControl = basetime * 60 * 1000;
4403         timeControl_2 = 0;
4404         timeIncrement = increment * 1000;
4405         movesPerSession = 0;
4406         gameInfo.timeControl = TimeControlTagValue();
4407         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4410     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4411     setbuf(debugFP, NULL);
4412   }
4413
4414         gameInfo.outOfBook = NULL;
4415
4416         /* Do we have the ratings? */
4417         if (strcmp(player1Name, white) == 0 &&
4418             strcmp(player2Name, black) == 0) {
4419             if (appData.debugMode)
4420               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4421                       player1Rating, player2Rating);
4422             gameInfo.whiteRating = player1Rating;
4423             gameInfo.blackRating = player2Rating;
4424         } else if (strcmp(player2Name, white) == 0 &&
4425                    strcmp(player1Name, black) == 0) {
4426             if (appData.debugMode)
4427               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4428                       player2Rating, player1Rating);
4429             gameInfo.whiteRating = player2Rating;
4430             gameInfo.blackRating = player1Rating;
4431         }
4432         player1Name[0] = player2Name[0] = NULLCHAR;
4433
4434         /* Silence shouts if requested */
4435         if (appData.quietPlay &&
4436             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4437             SendToICS(ics_prefix);
4438             SendToICS("set shout 0\n");
4439         }
4440     }
4441
4442     /* Deal with midgame name changes */
4443     if (!newGame) {
4444         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4445             if (gameInfo.white) free(gameInfo.white);
4446             gameInfo.white = StrSave(white);
4447         }
4448         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4449             if (gameInfo.black) free(gameInfo.black);
4450             gameInfo.black = StrSave(black);
4451         }
4452     }
4453
4454     /* Throw away game result if anything actually changes in examine mode */
4455     if (gameMode == IcsExamining && !newGame) {
4456         gameInfo.result = GameUnfinished;
4457         if (gameInfo.resultDetails != NULL) {
4458             free(gameInfo.resultDetails);
4459             gameInfo.resultDetails = NULL;
4460         }
4461     }
4462
4463     /* In pausing && IcsExamining mode, we ignore boards coming
4464        in if they are in a different variation than we are. */
4465     if (pauseExamInvalid) return;
4466     if (pausing && gameMode == IcsExamining) {
4467         if (moveNum <= pauseExamForwardMostMove) {
4468             pauseExamInvalid = TRUE;
4469             forwardMostMove = pauseExamForwardMostMove;
4470             return;
4471         }
4472     }
4473
4474   if (appData.debugMode) {
4475     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4476   }
4477     /* Parse the board */
4478     for (k = 0; k < ranks; k++) {
4479       for (j = 0; j < files; j++)
4480         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4481       if(gameInfo.holdingsWidth > 1) {
4482            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4483            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4484       }
4485     }
4486     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4487       board[5][BOARD_RGHT+1] = WhiteAngel;
4488       board[6][BOARD_RGHT+1] = WhiteMarshall;
4489       board[1][0] = BlackMarshall;
4490       board[2][0] = BlackAngel;
4491       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4492     }
4493     CopyBoard(boards[moveNum], board);
4494     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4495     if (moveNum == 0) {
4496         startedFromSetupPosition =
4497           !CompareBoards(board, initialPosition);
4498         if(startedFromSetupPosition)
4499             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4500     }
4501
4502     /* [HGM] Set castling rights. Take the outermost Rooks,
4503        to make it also work for FRC opening positions. Note that board12
4504        is really defective for later FRC positions, as it has no way to
4505        indicate which Rook can castle if they are on the same side of King.
4506        For the initial position we grant rights to the outermost Rooks,
4507        and remember thos rights, and we then copy them on positions
4508        later in an FRC game. This means WB might not recognize castlings with
4509        Rooks that have moved back to their original position as illegal,
4510        but in ICS mode that is not its job anyway.
4511     */
4512     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4513     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4514
4515         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4516             if(board[0][i] == WhiteRook) j = i;
4517         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4518         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4519             if(board[0][i] == WhiteRook) j = i;
4520         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4521         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4522             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4523         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4524         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4525             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4526         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4527
4528         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4529         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4530         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4531             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4532         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4533             if(board[BOARD_HEIGHT-1][k] == bKing)
4534                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4535         if(gameInfo.variant == VariantTwoKings) {
4536             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4537             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4538             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4539         }
4540     } else { int r;
4541         r = boards[moveNum][CASTLING][0] = initialRights[0];
4542         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4543         r = boards[moveNum][CASTLING][1] = initialRights[1];
4544         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4545         r = boards[moveNum][CASTLING][3] = initialRights[3];
4546         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4547         r = boards[moveNum][CASTLING][4] = initialRights[4];
4548         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4549         /* wildcastle kludge: always assume King has rights */
4550         r = boards[moveNum][CASTLING][2] = initialRights[2];
4551         r = boards[moveNum][CASTLING][5] = initialRights[5];
4552     }
4553     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4554     boards[moveNum][EP_STATUS] = EP_NONE;
4555     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4556     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4557     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4558
4559
4560     if (ics_getting_history == H_GOT_REQ_HEADER ||
4561         ics_getting_history == H_GOT_UNREQ_HEADER) {
4562         /* This was an initial position from a move list, not
4563            the current position */
4564         return;
4565     }
4566
4567     /* Update currentMove and known move number limits */
4568     newMove = newGame || moveNum > forwardMostMove;
4569
4570     if (newGame) {
4571         forwardMostMove = backwardMostMove = currentMove = moveNum;
4572         if (gameMode == IcsExamining && moveNum == 0) {
4573           /* Workaround for ICS limitation: we are not told the wild
4574              type when starting to examine a game.  But if we ask for
4575              the move list, the move list header will tell us */
4576             ics_getting_history = H_REQUESTED;
4577             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4578             SendToICS(str);
4579         }
4580     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4581                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4582 #if ZIPPY
4583         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4584         /* [HGM] applied this also to an engine that is silently watching        */
4585         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4586             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4587             gameInfo.variant == currentlyInitializedVariant) {
4588           takeback = forwardMostMove - moveNum;
4589           for (i = 0; i < takeback; i++) {
4590             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4591             SendToProgram("undo\n", &first);
4592           }
4593         }
4594 #endif
4595
4596         forwardMostMove = moveNum;
4597         if (!pausing || currentMove > forwardMostMove)
4598           currentMove = forwardMostMove;
4599     } else {
4600         /* New part of history that is not contiguous with old part */
4601         if (pausing && gameMode == IcsExamining) {
4602             pauseExamInvalid = TRUE;
4603             forwardMostMove = pauseExamForwardMostMove;
4604             return;
4605         }
4606         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4607 #if ZIPPY
4608             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4609                 // [HGM] when we will receive the move list we now request, it will be
4610                 // fed to the engine from the first move on. So if the engine is not
4611                 // in the initial position now, bring it there.
4612                 InitChessProgram(&first, 0);
4613             }
4614 #endif
4615             ics_getting_history = H_REQUESTED;
4616             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4617             SendToICS(str);
4618         }
4619         forwardMostMove = backwardMostMove = currentMove = moveNum;
4620     }
4621
4622     /* Update the clocks */
4623     if (strchr(elapsed_time, '.')) {
4624       /* Time is in ms */
4625       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4626       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4627     } else {
4628       /* Time is in seconds */
4629       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4630       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4631     }
4632
4633
4634 #if ZIPPY
4635     if (appData.zippyPlay && newGame &&
4636         gameMode != IcsObserving && gameMode != IcsIdle &&
4637         gameMode != IcsExamining)
4638       ZippyFirstBoard(moveNum, basetime, increment);
4639 #endif
4640
4641     /* Put the move on the move list, first converting
4642        to canonical algebraic form. */
4643     if (moveNum > 0) {
4644   if (appData.debugMode) {
4645     if (appData.debugMode) { int f = forwardMostMove;
4646         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4647                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4648                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4649     }
4650     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4651     fprintf(debugFP, "moveNum = %d\n", moveNum);
4652     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4653     setbuf(debugFP, NULL);
4654   }
4655         if (moveNum <= backwardMostMove) {
4656             /* We don't know what the board looked like before
4657                this move.  Punt. */
4658           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659             strcat(parseList[moveNum - 1], " ");
4660             strcat(parseList[moveNum - 1], elapsed_time);
4661             moveList[moveNum - 1][0] = NULLCHAR;
4662         } else if (strcmp(move_str, "none") == 0) {
4663             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4664             /* Again, we don't know what the board looked like;
4665                this is really the start of the game. */
4666             parseList[moveNum - 1][0] = NULLCHAR;
4667             moveList[moveNum - 1][0] = NULLCHAR;
4668             backwardMostMove = moveNum;
4669             startedFromSetupPosition = TRUE;
4670             fromX = fromY = toX = toY = -1;
4671         } else {
4672           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4673           //                 So we parse the long-algebraic move string in stead of the SAN move
4674           int valid; char buf[MSG_SIZ], *prom;
4675
4676           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4677                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4678           // str looks something like "Q/a1-a2"; kill the slash
4679           if(str[1] == '/')
4680             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4681           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4682           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4683                 strcat(buf, prom); // long move lacks promo specification!
4684           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4685                 if(appData.debugMode)
4686                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4687                 safeStrCpy(move_str, buf, MSG_SIZ);
4688           }
4689           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4690                                 &fromX, &fromY, &toX, &toY, &promoChar)
4691                || ParseOneMove(buf, moveNum - 1, &moveType,
4692                                 &fromX, &fromY, &toX, &toY, &promoChar);
4693           // end of long SAN patch
4694           if (valid) {
4695             (void) CoordsToAlgebraic(boards[moveNum - 1],
4696                                      PosFlags(moveNum - 1),
4697                                      fromY, fromX, toY, toX, promoChar,
4698                                      parseList[moveNum-1]);
4699             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4700               case MT_NONE:
4701               case MT_STALEMATE:
4702               default:
4703                 break;
4704               case MT_CHECK:
4705                 if(gameInfo.variant != VariantShogi)
4706                     strcat(parseList[moveNum - 1], "+");
4707                 break;
4708               case MT_CHECKMATE:
4709               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4710                 strcat(parseList[moveNum - 1], "#");
4711                 break;
4712             }
4713             strcat(parseList[moveNum - 1], " ");
4714             strcat(parseList[moveNum - 1], elapsed_time);
4715             /* currentMoveString is set as a side-effect of ParseOneMove */
4716             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4717             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4718             strcat(moveList[moveNum - 1], "\n");
4719
4720             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4721                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4722               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4723                 ChessSquare old, new = boards[moveNum][k][j];
4724                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4725                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4726                   if(old == new) continue;
4727                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4728                   else if(new == WhiteWazir || new == BlackWazir) {
4729                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4730                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4731                       else boards[moveNum][k][j] = old; // preserve type of Gold
4732                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4733                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4734               }
4735           } else {
4736             /* Move from ICS was illegal!?  Punt. */
4737             if (appData.debugMode) {
4738               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4739               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4740             }
4741             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4742             strcat(parseList[moveNum - 1], " ");
4743             strcat(parseList[moveNum - 1], elapsed_time);
4744             moveList[moveNum - 1][0] = NULLCHAR;
4745             fromX = fromY = toX = toY = -1;
4746           }
4747         }
4748   if (appData.debugMode) {
4749     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4750     setbuf(debugFP, NULL);
4751   }
4752
4753 #if ZIPPY
4754         /* Send move to chess program (BEFORE animating it). */
4755         if (appData.zippyPlay && !newGame && newMove &&
4756            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4757
4758             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4759                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4760                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4761                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4762                             move_str);
4763                     DisplayError(str, 0);
4764                 } else {
4765                     if (first.sendTime) {
4766                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4767                     }
4768                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4769                     if (firstMove && !bookHit) {
4770                         firstMove = FALSE;
4771                         if (first.useColors) {
4772                           SendToProgram(gameMode == IcsPlayingWhite ?
4773                                         "white\ngo\n" :
4774                                         "black\ngo\n", &first);
4775                         } else {
4776                           SendToProgram("go\n", &first);
4777                         }
4778                         first.maybeThinking = TRUE;
4779                     }
4780                 }
4781             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4782               if (moveList[moveNum - 1][0] == NULLCHAR) {
4783                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4784                 DisplayError(str, 0);
4785               } else {
4786                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4787                 SendMoveToProgram(moveNum - 1, &first);
4788               }
4789             }
4790         }
4791 #endif
4792     }
4793
4794     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4795         /* If move comes from a remote source, animate it.  If it
4796            isn't remote, it will have already been animated. */
4797         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4798             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4799         }
4800         if (!pausing && appData.highlightLastMove) {
4801             SetHighlights(fromX, fromY, toX, toY);
4802         }
4803     }
4804
4805     /* Start the clocks */
4806     whiteFlag = blackFlag = FALSE;
4807     appData.clockMode = !(basetime == 0 && increment == 0);
4808     if (ticking == 0) {
4809       ics_clock_paused = TRUE;
4810       StopClocks();
4811     } else if (ticking == 1) {
4812       ics_clock_paused = FALSE;
4813     }
4814     if (gameMode == IcsIdle ||
4815         relation == RELATION_OBSERVING_STATIC ||
4816         relation == RELATION_EXAMINING ||
4817         ics_clock_paused)
4818       DisplayBothClocks();
4819     else
4820       StartClocks();
4821
4822     /* Display opponents and material strengths */
4823     if (gameInfo.variant != VariantBughouse &&
4824         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4825         if (tinyLayout || smallLayout) {
4826             if(gameInfo.variant == VariantNormal)
4827               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4828                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4829                     basetime, increment);
4830             else
4831               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4832                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4833                     basetime, increment, (int) gameInfo.variant);
4834         } else {
4835             if(gameInfo.variant == VariantNormal)
4836               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4837                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4838                     basetime, increment);
4839             else
4840               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4841                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4842                     basetime, increment, VariantName(gameInfo.variant));
4843         }
4844         DisplayTitle(str);
4845   if (appData.debugMode) {
4846     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4847   }
4848     }
4849
4850
4851     /* Display the board */
4852     if (!pausing && !appData.noGUI) {
4853
4854       if (appData.premove)
4855           if (!gotPremove ||
4856              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4857              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4858               ClearPremoveHighlights();
4859
4860       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4861         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4862       DrawPosition(j, boards[currentMove]);
4863
4864       DisplayMove(moveNum - 1);
4865       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4866             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4867               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4868         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4869       }
4870     }
4871
4872     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4873 #if ZIPPY
4874     if(bookHit) { // [HGM] book: simulate book reply
4875         static char bookMove[MSG_SIZ]; // a bit generous?
4876
4877         programStats.nodes = programStats.depth = programStats.time =
4878         programStats.score = programStats.got_only_move = 0;
4879         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4880
4881         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4882         strcat(bookMove, bookHit);
4883         HandleMachineMove(bookMove, &first);
4884     }
4885 #endif
4886 }
4887
4888 void
4889 GetMoveListEvent ()
4890 {
4891     char buf[MSG_SIZ];
4892     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4893         ics_getting_history = H_REQUESTED;
4894         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4895         SendToICS(buf);
4896     }
4897 }
4898
4899 void
4900 AnalysisPeriodicEvent (int force)
4901 {
4902     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4903          && !force) || !appData.periodicUpdates)
4904       return;
4905
4906     /* Send . command to Crafty to collect stats */
4907     SendToProgram(".\n", &first);
4908
4909     /* Don't send another until we get a response (this makes
4910        us stop sending to old Crafty's which don't understand
4911        the "." command (sending illegal cmds resets node count & time,
4912        which looks bad)) */
4913     programStats.ok_to_send = 0;
4914 }
4915
4916 void
4917 ics_update_width (int new_width)
4918 {
4919         ics_printf("set width %d\n", new_width);
4920 }
4921
4922 void
4923 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4924 {
4925     char buf[MSG_SIZ];
4926
4927     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4928         // null move in variant where engine does not understand it (for analysis purposes)
4929         SendBoard(cps, moveNum + 1); // send position after move in stead.
4930         return;
4931     }
4932     if (cps->useUsermove) {
4933       SendToProgram("usermove ", cps);
4934     }
4935     if (cps->useSAN) {
4936       char *space;
4937       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4938         int len = space - parseList[moveNum];
4939         memcpy(buf, parseList[moveNum], len);
4940         buf[len++] = '\n';
4941         buf[len] = NULLCHAR;
4942       } else {
4943         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4944       }
4945       SendToProgram(buf, cps);
4946     } else {
4947       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4948         AlphaRank(moveList[moveNum], 4);
4949         SendToProgram(moveList[moveNum], cps);
4950         AlphaRank(moveList[moveNum], 4); // and back
4951       } else
4952       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4953        * the engine. It would be nice to have a better way to identify castle
4954        * moves here. */
4955       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4956                                                                          && cps->useOOCastle) {
4957         int fromX = moveList[moveNum][0] - AAA;
4958         int fromY = moveList[moveNum][1] - ONE;
4959         int toX = moveList[moveNum][2] - AAA;
4960         int toY = moveList[moveNum][3] - ONE;
4961         if((boards[moveNum][fromY][fromX] == WhiteKing
4962             && boards[moveNum][toY][toX] == WhiteRook)
4963            || (boards[moveNum][fromY][fromX] == BlackKing
4964                && boards[moveNum][toY][toX] == BlackRook)) {
4965           if(toX > fromX) SendToProgram("O-O\n", cps);
4966           else SendToProgram("O-O-O\n", cps);
4967         }
4968         else SendToProgram(moveList[moveNum], cps);
4969       } else
4970       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4971         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4972           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4973           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4974                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4975         } else
4976           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4977                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4978         SendToProgram(buf, cps);
4979       }
4980       else SendToProgram(moveList[moveNum], cps);
4981       /* End of additions by Tord */
4982     }
4983
4984     /* [HGM] setting up the opening has brought engine in force mode! */
4985     /*       Send 'go' if we are in a mode where machine should play. */
4986     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4987         (gameMode == TwoMachinesPlay   ||
4988 #if ZIPPY
4989          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4990 #endif
4991          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4992         SendToProgram("go\n", cps);
4993   if (appData.debugMode) {
4994     fprintf(debugFP, "(extra)\n");
4995   }
4996     }
4997     setboardSpoiledMachineBlack = 0;
4998 }
4999
5000 void
5001 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5002 {
5003     char user_move[MSG_SIZ];
5004     char suffix[4];
5005
5006     if(gameInfo.variant == VariantSChess && promoChar) {
5007         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5008         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5009     } else suffix[0] = NULLCHAR;
5010
5011     switch (moveType) {
5012       default:
5013         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5014                 (int)moveType, fromX, fromY, toX, toY);
5015         DisplayError(user_move + strlen("say "), 0);
5016         break;
5017       case WhiteKingSideCastle:
5018       case BlackKingSideCastle:
5019       case WhiteQueenSideCastleWild:
5020       case BlackQueenSideCastleWild:
5021       /* PUSH Fabien */
5022       case WhiteHSideCastleFR:
5023       case BlackHSideCastleFR:
5024       /* POP Fabien */
5025         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5026         break;
5027       case WhiteQueenSideCastle:
5028       case BlackQueenSideCastle:
5029       case WhiteKingSideCastleWild:
5030       case BlackKingSideCastleWild:
5031       /* PUSH Fabien */
5032       case WhiteASideCastleFR:
5033       case BlackASideCastleFR:
5034       /* POP Fabien */
5035         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5036         break;
5037       case WhiteNonPromotion:
5038       case BlackNonPromotion:
5039         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5040         break;
5041       case WhitePromotion:
5042       case BlackPromotion:
5043         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5044           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5045                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5046                 PieceToChar(WhiteFerz));
5047         else if(gameInfo.variant == VariantGreat)
5048           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5049                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5050                 PieceToChar(WhiteMan));
5051         else
5052           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5053                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5054                 promoChar);
5055         break;
5056       case WhiteDrop:
5057       case BlackDrop:
5058       drop:
5059         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5060                  ToUpper(PieceToChar((ChessSquare) fromX)),
5061                  AAA + toX, ONE + toY);
5062         break;
5063       case IllegalMove:  /* could be a variant we don't quite understand */
5064         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5065       case NormalMove:
5066       case WhiteCapturesEnPassant:
5067       case BlackCapturesEnPassant:
5068         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5070         break;
5071     }
5072     SendToICS(user_move);
5073     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5074         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5075 }
5076
5077 void
5078 UploadGameEvent ()
5079 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5080     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5081     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5082     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5083       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5084       return;
5085     }
5086     if(gameMode != IcsExamining) { // is this ever not the case?
5087         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5088
5089         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5090           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5091         } else { // on FICS we must first go to general examine mode
5092           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5093         }
5094         if(gameInfo.variant != VariantNormal) {
5095             // try figure out wild number, as xboard names are not always valid on ICS
5096             for(i=1; i<=36; i++) {
5097               snprintf(buf, MSG_SIZ, "wild/%d", i);
5098                 if(StringToVariant(buf) == gameInfo.variant) break;
5099             }
5100             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5101             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5102             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5103         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5104         SendToICS(ics_prefix);
5105         SendToICS(buf);
5106         if(startedFromSetupPosition || backwardMostMove != 0) {
5107           fen = PositionToFEN(backwardMostMove, NULL);
5108           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5109             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5110             SendToICS(buf);
5111           } else { // FICS: everything has to set by separate bsetup commands
5112             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5113             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5114             SendToICS(buf);
5115             if(!WhiteOnMove(backwardMostMove)) {
5116                 SendToICS("bsetup tomove black\n");
5117             }
5118             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5119             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5120             SendToICS(buf);
5121             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5122             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5123             SendToICS(buf);
5124             i = boards[backwardMostMove][EP_STATUS];
5125             if(i >= 0) { // set e.p.
5126               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5127                 SendToICS(buf);
5128             }
5129             bsetup++;
5130           }
5131         }
5132       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5133             SendToICS("bsetup done\n"); // switch to normal examining.
5134     }
5135     for(i = backwardMostMove; i<last; i++) {
5136         char buf[20];
5137         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5138         SendToICS(buf);
5139     }
5140     SendToICS(ics_prefix);
5141     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5142 }
5143
5144 void
5145 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5146 {
5147     if (rf == DROP_RANK) {
5148       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5149       sprintf(move, "%c@%c%c\n",
5150                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5151     } else {
5152         if (promoChar == 'x' || promoChar == NULLCHAR) {
5153           sprintf(move, "%c%c%c%c\n",
5154                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5155         } else {
5156             sprintf(move, "%c%c%c%c%c\n",
5157                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5158         }
5159     }
5160 }
5161
5162 void
5163 ProcessICSInitScript (FILE *f)
5164 {
5165     char buf[MSG_SIZ];
5166
5167     while (fgets(buf, MSG_SIZ, f)) {
5168         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5169     }
5170
5171     fclose(f);
5172 }
5173
5174
5175 static int lastX, lastY, selectFlag, dragging;
5176
5177 void
5178 Sweep (int step)
5179 {
5180     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5181     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5182     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5183     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5184     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5185     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5186     do {
5187         promoSweep -= step;
5188         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5189         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5190         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5191         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5192         if(!step) step = -1;
5193     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5194             appData.testLegality && (promoSweep == king ||
5195             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5196     if(toX >= 0) {
5197         int victim = boards[currentMove][toY][toX];
5198         boards[currentMove][toY][toX] = promoSweep;
5199         DrawPosition(FALSE, boards[currentMove]);
5200         boards[currentMove][toY][toX] = victim;
5201     } else
5202     ChangeDragPiece(promoSweep);
5203 }
5204
5205 int
5206 PromoScroll (int x, int y)
5207 {
5208   int step = 0;
5209
5210   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5211   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5212   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5213   if(!step) return FALSE;
5214   lastX = x; lastY = y;
5215   if((promoSweep < BlackPawn) == flipView) step = -step;
5216   if(step > 0) selectFlag = 1;
5217   if(!selectFlag) Sweep(step);
5218   return FALSE;
5219 }
5220
5221 void
5222 NextPiece (int step)
5223 {
5224     ChessSquare piece = boards[currentMove][toY][toX];
5225     do {
5226         pieceSweep -= step;
5227         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5228         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5229         if(!step) step = -1;
5230     } while(PieceToChar(pieceSweep) == '.');
5231     boards[currentMove][toY][toX] = pieceSweep;
5232     DrawPosition(FALSE, boards[currentMove]);
5233     boards[currentMove][toY][toX] = piece;
5234 }
5235 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5236 void
5237 AlphaRank (char *move, int n)
5238 {
5239 //    char *p = move, c; int x, y;
5240
5241     if (appData.debugMode) {
5242         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5243     }
5244
5245     if(move[1]=='*' &&
5246        move[2]>='0' && move[2]<='9' &&
5247        move[3]>='a' && move[3]<='x'    ) {
5248         move[1] = '@';
5249         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5250         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5251     } else
5252     if(move[0]>='0' && move[0]<='9' &&
5253        move[1]>='a' && move[1]<='x' &&
5254        move[2]>='0' && move[2]<='9' &&
5255        move[3]>='a' && move[3]<='x'    ) {
5256         /* input move, Shogi -> normal */
5257         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5258         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5259         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5260         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5261     } else
5262     if(move[1]=='@' &&
5263        move[3]>='0' && move[3]<='9' &&
5264        move[2]>='a' && move[2]<='x'    ) {
5265         move[1] = '*';
5266         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5267         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5268     } else
5269     if(
5270        move[0]>='a' && move[0]<='x' &&
5271        move[3]>='0' && move[3]<='9' &&
5272        move[2]>='a' && move[2]<='x'    ) {
5273          /* output move, normal -> Shogi */
5274         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5275         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5276         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5277         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5278         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5279     }
5280     if (appData.debugMode) {
5281         fprintf(debugFP, "   out = '%s'\n", move);
5282     }
5283 }
5284
5285 char yy_textstr[8000];
5286
5287 /* Parser for moves from gnuchess, ICS, or user typein box */
5288 Boolean
5289 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5290 {
5291     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5292
5293     switch (*moveType) {
5294       case WhitePromotion:
5295       case BlackPromotion:
5296       case WhiteNonPromotion:
5297       case BlackNonPromotion:
5298       case NormalMove:
5299       case WhiteCapturesEnPassant:
5300       case BlackCapturesEnPassant:
5301       case WhiteKingSideCastle:
5302       case WhiteQueenSideCastle:
5303       case BlackKingSideCastle:
5304       case BlackQueenSideCastle:
5305       case WhiteKingSideCastleWild:
5306       case WhiteQueenSideCastleWild:
5307       case BlackKingSideCastleWild:
5308       case BlackQueenSideCastleWild:
5309       /* Code added by Tord: */
5310       case WhiteHSideCastleFR:
5311       case WhiteASideCastleFR:
5312       case BlackHSideCastleFR:
5313       case BlackASideCastleFR:
5314       /* End of code added by Tord */
5315       case IllegalMove:         /* bug or odd chess variant */
5316         *fromX = currentMoveString[0] - AAA;
5317         *fromY = currentMoveString[1] - ONE;
5318         *toX = currentMoveString[2] - AAA;
5319         *toY = currentMoveString[3] - ONE;
5320         *promoChar = currentMoveString[4];
5321         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5322             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5323     if (appData.debugMode) {
5324         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5325     }
5326             *fromX = *fromY = *toX = *toY = 0;
5327             return FALSE;
5328         }
5329         if (appData.testLegality) {
5330           return (*moveType != IllegalMove);
5331         } else {
5332           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5333                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5334         }
5335
5336       case WhiteDrop:
5337       case BlackDrop:
5338         *fromX = *moveType == WhiteDrop ?
5339           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5340           (int) CharToPiece(ToLower(currentMoveString[0]));
5341         *fromY = DROP_RANK;
5342         *toX = currentMoveString[2] - AAA;
5343         *toY = currentMoveString[3] - ONE;
5344         *promoChar = NULLCHAR;
5345         return TRUE;
5346
5347       case AmbiguousMove:
5348       case ImpossibleMove:
5349       case EndOfFile:
5350       case ElapsedTime:
5351       case Comment:
5352       case PGNTag:
5353       case NAG:
5354       case WhiteWins:
5355       case BlackWins:
5356       case GameIsDrawn:
5357       default:
5358     if (appData.debugMode) {
5359         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5360     }
5361         /* bug? */
5362         *fromX = *fromY = *toX = *toY = 0;
5363         *promoChar = NULLCHAR;
5364         return FALSE;
5365     }
5366 }
5367
5368 Boolean pushed = FALSE;
5369 char *lastParseAttempt;
5370
5371 void
5372 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5373 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5374   int fromX, fromY, toX, toY; char promoChar;
5375   ChessMove moveType;
5376   Boolean valid;
5377   int nr = 0;
5378
5379   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5380     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5381     pushed = TRUE;
5382   }
5383   endPV = forwardMostMove;
5384   do {
5385     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5386     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5387     lastParseAttempt = pv;
5388     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5389     if(!valid && nr == 0 &&
5390        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5391         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5392         // Hande case where played move is different from leading PV move
5393         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5394         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5395         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5396         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5397           endPV += 2; // if position different, keep this
5398           moveList[endPV-1][0] = fromX + AAA;
5399           moveList[endPV-1][1] = fromY + ONE;
5400           moveList[endPV-1][2] = toX + AAA;
5401           moveList[endPV-1][3] = toY + ONE;
5402           parseList[endPV-1][0] = NULLCHAR;
5403           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5404         }
5405       }
5406     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5407     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5408     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5409     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5410         valid++; // allow comments in PV
5411         continue;
5412     }
5413     nr++;
5414     if(endPV+1 > framePtr) break; // no space, truncate
5415     if(!valid) break;
5416     endPV++;
5417     CopyBoard(boards[endPV], boards[endPV-1]);
5418     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5419     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5420     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5421     CoordsToAlgebraic(boards[endPV - 1],
5422                              PosFlags(endPV - 1),
5423                              fromY, fromX, toY, toX, promoChar,
5424                              parseList[endPV - 1]);
5425   } while(valid);
5426   if(atEnd == 2) return; // used hidden, for PV conversion
5427   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5428   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5429   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5430                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5431   DrawPosition(TRUE, boards[currentMove]);
5432 }
5433
5434 int
5435 MultiPV (ChessProgramState *cps)
5436 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5437         int i;
5438         for(i=0; i<cps->nrOptions; i++)
5439             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5440                 return i;
5441         return -1;
5442 }
5443
5444 Boolean
5445 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5446 {
5447         int startPV, multi, lineStart, origIndex = index;
5448         char *p, buf2[MSG_SIZ];
5449
5450         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5451         lastX = x; lastY = y;
5452         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5453         lineStart = startPV = index;
5454         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5455         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5456         index = startPV;
5457         do{ while(buf[index] && buf[index] != '\n') index++;
5458         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5459         buf[index] = 0;
5460         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5461                 int n = first.option[multi].value;
5462                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5463                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5464                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5465                 first.option[multi].value = n;
5466                 *start = *end = 0;
5467                 return FALSE;
5468         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5469                 ExcludeClick(origIndex - lineStart);
5470                 return FALSE;
5471         }
5472         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5473         *start = startPV; *end = index-1;
5474         return TRUE;
5475 }
5476
5477 char *
5478 PvToSAN (char *pv)
5479 {
5480         static char buf[10*MSG_SIZ];
5481         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5482         *buf = NULLCHAR;
5483         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5484         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5485         for(i = forwardMostMove; i<endPV; i++){
5486             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5487             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5488             k += strlen(buf+k);
5489         }
5490         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5491         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5492         endPV = savedEnd;
5493         return buf;
5494 }
5495
5496 Boolean
5497 LoadPV (int x, int y)
5498 { // called on right mouse click to load PV
5499   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5500   lastX = x; lastY = y;
5501   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5502   return TRUE;
5503 }
5504
5505 void
5506 UnLoadPV ()
5507 {
5508   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5509   if(endPV < 0) return;
5510   if(appData.autoCopyPV) CopyFENToClipboard();
5511   endPV = -1;
5512   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5513         Boolean saveAnimate = appData.animate;
5514         if(pushed) {
5515             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5516                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5517             } else storedGames--; // abandon shelved tail of original game
5518         }
5519         pushed = FALSE;
5520         forwardMostMove = currentMove;
5521         currentMove = oldFMM;
5522         appData.animate = FALSE;
5523         ToNrEvent(forwardMostMove);
5524         appData.animate = saveAnimate;
5525   }
5526   currentMove = forwardMostMove;
5527   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5528   ClearPremoveHighlights();
5529   DrawPosition(TRUE, boards[currentMove]);
5530 }
5531
5532 void
5533 MovePV (int x, int y, int h)
5534 { // step through PV based on mouse coordinates (called on mouse move)
5535   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5536
5537   // we must somehow check if right button is still down (might be released off board!)
5538   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5539   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5540   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5541   if(!step) return;
5542   lastX = x; lastY = y;
5543
5544   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5545   if(endPV < 0) return;
5546   if(y < margin) step = 1; else
5547   if(y > h - margin) step = -1;
5548   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5549   currentMove += step;
5550   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5551   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5552                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5553   DrawPosition(FALSE, boards[currentMove]);
5554 }
5555
5556
5557 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5558 // All positions will have equal probability, but the current method will not provide a unique
5559 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5560 #define DARK 1
5561 #define LITE 2
5562 #define ANY 3
5563
5564 int squaresLeft[4];
5565 int piecesLeft[(int)BlackPawn];
5566 int seed, nrOfShuffles;
5567
5568 void
5569 GetPositionNumber ()
5570 {       // sets global variable seed
5571         int i;
5572
5573         seed = appData.defaultFrcPosition;
5574         if(seed < 0) { // randomize based on time for negative FRC position numbers
5575                 for(i=0; i<50; i++) seed += random();
5576                 seed = random() ^ random() >> 8 ^ random() << 8;
5577                 if(seed<0) seed = -seed;
5578         }
5579 }
5580
5581 int
5582 put (Board board, int pieceType, int rank, int n, int shade)
5583 // put the piece on the (n-1)-th empty squares of the given shade
5584 {
5585         int i;
5586
5587         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5588                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5589                         board[rank][i] = (ChessSquare) pieceType;
5590                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5591                         squaresLeft[ANY]--;
5592                         piecesLeft[pieceType]--;
5593                         return i;
5594                 }
5595         }
5596         return -1;
5597 }
5598
5599
5600 void
5601 AddOnePiece (Board board, int pieceType, int rank, int shade)
5602 // calculate where the next piece goes, (any empty square), and put it there
5603 {
5604         int i;
5605
5606         i = seed % squaresLeft[shade];
5607         nrOfShuffles *= squaresLeft[shade];
5608         seed /= squaresLeft[shade];
5609         put(board, pieceType, rank, i, shade);
5610 }
5611
5612 void
5613 AddTwoPieces (Board board, int pieceType, int rank)
5614 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5615 {
5616         int i, n=squaresLeft[ANY], j=n-1, k;
5617
5618         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5619         i = seed % k;  // pick one
5620         nrOfShuffles *= k;
5621         seed /= k;
5622         while(i >= j) i -= j--;
5623         j = n - 1 - j; i += j;
5624         put(board, pieceType, rank, j, ANY);
5625         put(board, pieceType, rank, i, ANY);
5626 }
5627
5628 void
5629 SetUpShuffle (Board board, int number)
5630 {
5631         int i, p, first=1;
5632
5633         GetPositionNumber(); nrOfShuffles = 1;
5634
5635         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5636         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5637         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5638
5639         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5640
5641         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5642             p = (int) board[0][i];
5643             if(p < (int) BlackPawn) piecesLeft[p] ++;
5644             board[0][i] = EmptySquare;
5645         }
5646
5647         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5648             // shuffles restricted to allow normal castling put KRR first
5649             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5650                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5651             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5652                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5653             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5654                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5655             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5656                 put(board, WhiteRook, 0, 0, ANY);
5657             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5658         }
5659
5660         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5661             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5662             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5663                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5664                 while(piecesLeft[p] >= 2) {
5665                     AddOnePiece(board, p, 0, LITE);
5666                     AddOnePiece(board, p, 0, DARK);
5667                 }
5668                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5669             }
5670
5671         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5672             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5673             // but we leave King and Rooks for last, to possibly obey FRC restriction
5674             if(p == (int)WhiteRook) continue;
5675             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5676             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5677         }
5678
5679         // now everything is placed, except perhaps King (Unicorn) and Rooks
5680
5681         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5682             // Last King gets castling rights
5683             while(piecesLeft[(int)WhiteUnicorn]) {
5684                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5685                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5686             }
5687
5688             while(piecesLeft[(int)WhiteKing]) {
5689                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5690                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5691             }
5692
5693
5694         } else {
5695             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5696             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5697         }
5698
5699         // Only Rooks can be left; simply place them all
5700         while(piecesLeft[(int)WhiteRook]) {
5701                 i = put(board, WhiteRook, 0, 0, ANY);
5702                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5703                         if(first) {
5704                                 first=0;
5705                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5706                         }
5707                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5708                 }
5709         }
5710         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5711             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5712         }
5713
5714         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5715 }
5716
5717 int
5718 SetCharTable (char *table, const char * map)
5719 /* [HGM] moved here from winboard.c because of its general usefulness */
5720 /*       Basically a safe strcpy that uses the last character as King */
5721 {
5722     int result = FALSE; int NrPieces;
5723
5724     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5725                     && NrPieces >= 12 && !(NrPieces&1)) {
5726         int i; /* [HGM] Accept even length from 12 to 34 */
5727
5728         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5729         for( i=0; i<NrPieces/2-1; i++ ) {
5730             table[i] = map[i];
5731             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5732         }
5733         table[(int) WhiteKing]  = map[NrPieces/2-1];
5734         table[(int) BlackKing]  = map[NrPieces-1];
5735
5736         result = TRUE;
5737     }
5738
5739     return result;
5740 }
5741
5742 void
5743 Prelude (Board board)
5744 {       // [HGM] superchess: random selection of exo-pieces
5745         int i, j, k; ChessSquare p;
5746         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5747
5748         GetPositionNumber(); // use FRC position number
5749
5750         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5751             SetCharTable(pieceToChar, appData.pieceToCharTable);
5752             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5753                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5754         }
5755
5756         j = seed%4;                 seed /= 4;
5757         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5758         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5759         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5760         j = seed%3 + (seed%3 >= j); seed /= 3;
5761         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5762         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5763         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5764         j = seed%3;                 seed /= 3;
5765         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5766         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5767         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5768         j = seed%2 + (seed%2 >= j); seed /= 2;
5769         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5770         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5771         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5772         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5773         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5774         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5775         put(board, exoPieces[0],    0, 0, ANY);
5776         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5777 }
5778
5779 void
5780 InitPosition (int redraw)
5781 {
5782     ChessSquare (* pieces)[BOARD_FILES];
5783     int i, j, pawnRow, overrule,
5784     oldx = gameInfo.boardWidth,
5785     oldy = gameInfo.boardHeight,
5786     oldh = gameInfo.holdingsWidth;
5787     static int oldv;
5788
5789     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5790
5791     /* [AS] Initialize pv info list [HGM] and game status */
5792     {
5793         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5794             pvInfoList[i].depth = 0;
5795             boards[i][EP_STATUS] = EP_NONE;
5796             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5797         }
5798
5799         initialRulePlies = 0; /* 50-move counter start */
5800
5801         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5802         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5803     }
5804
5805
5806     /* [HGM] logic here is completely changed. In stead of full positions */
5807     /* the initialized data only consist of the two backranks. The switch */
5808     /* selects which one we will use, which is than copied to the Board   */
5809     /* initialPosition, which for the rest is initialized by Pawns and    */
5810     /* empty squares. This initial position is then copied to boards[0],  */
5811     /* possibly after shuffling, so that it remains available.            */
5812
5813     gameInfo.holdingsWidth = 0; /* default board sizes */
5814     gameInfo.boardWidth    = 8;
5815     gameInfo.boardHeight   = 8;
5816     gameInfo.holdingsSize  = 0;
5817     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5818     for(i=0; i<BOARD_FILES-2; i++)
5819       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5820     initialPosition[EP_STATUS] = EP_NONE;
5821     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5822     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5823          SetCharTable(pieceNickName, appData.pieceNickNames);
5824     else SetCharTable(pieceNickName, "............");
5825     pieces = FIDEArray;
5826
5827     switch (gameInfo.variant) {
5828     case VariantFischeRandom:
5829       shuffleOpenings = TRUE;
5830     default:
5831       break;
5832     case VariantShatranj:
5833       pieces = ShatranjArray;
5834       nrCastlingRights = 0;
5835       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5836       break;
5837     case VariantMakruk:
5838       pieces = makrukArray;
5839       nrCastlingRights = 0;
5840       startedFromSetupPosition = TRUE;
5841       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5842       break;
5843     case VariantTwoKings:
5844       pieces = twoKingsArray;
5845       break;
5846     case VariantGrand:
5847       pieces = GrandArray;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5850       gameInfo.boardWidth = 10;
5851       gameInfo.boardHeight = 10;
5852       gameInfo.holdingsSize = 7;
5853       break;
5854     case VariantCapaRandom:
5855       shuffleOpenings = TRUE;
5856     case VariantCapablanca:
5857       pieces = CapablancaArray;
5858       gameInfo.boardWidth = 10;
5859       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5860       break;
5861     case VariantGothic:
5862       pieces = GothicArray;
5863       gameInfo.boardWidth = 10;
5864       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865       break;
5866     case VariantSChess:
5867       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5868       gameInfo.holdingsSize = 7;
5869       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5870       break;
5871     case VariantJanus:
5872       pieces = JanusArray;
5873       gameInfo.boardWidth = 10;
5874       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5875       nrCastlingRights = 6;
5876         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5877         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5878         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5879         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5880         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5881         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5882       break;
5883     case VariantFalcon:
5884       pieces = FalconArray;
5885       gameInfo.boardWidth = 10;
5886       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5887       break;
5888     case VariantXiangqi:
5889       pieces = XiangqiArray;
5890       gameInfo.boardWidth  = 9;
5891       gameInfo.boardHeight = 10;
5892       nrCastlingRights = 0;
5893       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5894       break;
5895     case VariantShogi:
5896       pieces = ShogiArray;
5897       gameInfo.boardWidth  = 9;
5898       gameInfo.boardHeight = 9;
5899       gameInfo.holdingsSize = 7;
5900       nrCastlingRights = 0;
5901       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5902       break;
5903     case VariantCourier:
5904       pieces = CourierArray;
5905       gameInfo.boardWidth  = 12;
5906       nrCastlingRights = 0;
5907       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5908       break;
5909     case VariantKnightmate:
5910       pieces = KnightmateArray;
5911       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5912       break;
5913     case VariantSpartan:
5914       pieces = SpartanArray;
5915       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5916       break;
5917     case VariantFairy:
5918       pieces = fairyArray;
5919       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5920       break;
5921     case VariantGreat:
5922       pieces = GreatArray;
5923       gameInfo.boardWidth = 10;
5924       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5925       gameInfo.holdingsSize = 8;
5926       break;
5927     case VariantSuper:
5928       pieces = FIDEArray;
5929       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5930       gameInfo.holdingsSize = 8;
5931       startedFromSetupPosition = TRUE;
5932       break;
5933     case VariantCrazyhouse:
5934     case VariantBughouse:
5935       pieces = FIDEArray;
5936       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5937       gameInfo.holdingsSize = 5;
5938       break;
5939     case VariantWildCastle:
5940       pieces = FIDEArray;
5941       /* !!?shuffle with kings guaranteed to be on d or e file */
5942       shuffleOpenings = 1;
5943       break;
5944     case VariantNoCastle:
5945       pieces = FIDEArray;
5946       nrCastlingRights = 0;
5947       /* !!?unconstrained back-rank shuffle */
5948       shuffleOpenings = 1;
5949       break;
5950     }
5951
5952     overrule = 0;
5953     if(appData.NrFiles >= 0) {
5954         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5955         gameInfo.boardWidth = appData.NrFiles;
5956     }
5957     if(appData.NrRanks >= 0) {
5958         gameInfo.boardHeight = appData.NrRanks;
5959     }
5960     if(appData.holdingsSize >= 0) {
5961         i = appData.holdingsSize;
5962         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5963         gameInfo.holdingsSize = i;
5964     }
5965     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5966     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5967         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5968
5969     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5970     if(pawnRow < 1) pawnRow = 1;
5971     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5972
5973     /* User pieceToChar list overrules defaults */
5974     if(appData.pieceToCharTable != NULL)
5975         SetCharTable(pieceToChar, appData.pieceToCharTable);
5976
5977     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5978
5979         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5980             s = (ChessSquare) 0; /* account holding counts in guard band */
5981         for( i=0; i<BOARD_HEIGHT; i++ )
5982             initialPosition[i][j] = s;
5983
5984         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5985         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5986         initialPosition[pawnRow][j] = WhitePawn;
5987         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5988         if(gameInfo.variant == VariantXiangqi) {
5989             if(j&1) {
5990                 initialPosition[pawnRow][j] =
5991                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5992                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5993                    initialPosition[2][j] = WhiteCannon;
5994                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5995                 }
5996             }
5997         }
5998         if(gameInfo.variant == VariantGrand) {
5999             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6000                initialPosition[0][j] = WhiteRook;
6001                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6002             }
6003         }
6004         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6005     }
6006     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6007
6008             j=BOARD_LEFT+1;
6009             initialPosition[1][j] = WhiteBishop;
6010             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6011             j=BOARD_RGHT-2;
6012             initialPosition[1][j] = WhiteRook;
6013             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6014     }
6015
6016     if( nrCastlingRights == -1) {
6017         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6018         /*       This sets default castling rights from none to normal corners   */
6019         /* Variants with other castling rights must set them themselves above    */
6020         nrCastlingRights = 6;
6021
6022         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6023         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6024         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6025         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6026         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6027         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6028      }
6029
6030      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6031      if(gameInfo.variant == VariantGreat) { // promotion commoners
6032         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6033         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6034         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6035         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6036      }
6037      if( gameInfo.variant == VariantSChess ) {
6038       initialPosition[1][0] = BlackMarshall;
6039       initialPosition[2][0] = BlackAngel;
6040       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6041       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6042       initialPosition[1][1] = initialPosition[2][1] = 
6043       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6044      }
6045   if (appData.debugMode) {
6046     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6047   }
6048     if(shuffleOpenings) {
6049         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6050         startedFromSetupPosition = TRUE;
6051     }
6052     if(startedFromPositionFile) {
6053       /* [HGM] loadPos: use PositionFile for every new game */
6054       CopyBoard(initialPosition, filePosition);
6055       for(i=0; i<nrCastlingRights; i++)
6056           initialRights[i] = filePosition[CASTLING][i];
6057       startedFromSetupPosition = TRUE;
6058     }
6059
6060     CopyBoard(boards[0], initialPosition);
6061
6062     if(oldx != gameInfo.boardWidth ||
6063        oldy != gameInfo.boardHeight ||
6064        oldv != gameInfo.variant ||
6065        oldh != gameInfo.holdingsWidth
6066                                          )
6067             InitDrawingSizes(-2 ,0);
6068
6069     oldv = gameInfo.variant;
6070     if (redraw)
6071       DrawPosition(TRUE, boards[currentMove]);
6072 }
6073
6074 void
6075 SendBoard (ChessProgramState *cps, int moveNum)
6076 {
6077     char message[MSG_SIZ];
6078
6079     if (cps->useSetboard) {
6080       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6081       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6082       SendToProgram(message, cps);
6083       free(fen);
6084
6085     } else {
6086       ChessSquare *bp;
6087       int i, j, left=0, right=BOARD_WIDTH;
6088       /* Kludge to set black to move, avoiding the troublesome and now
6089        * deprecated "black" command.
6090        */
6091       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6092         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6093
6094       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6095
6096       SendToProgram("edit\n", cps);
6097       SendToProgram("#\n", cps);
6098       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6099         bp = &boards[moveNum][i][left];
6100         for (j = left; j < right; j++, bp++) {
6101           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6102           if ((int) *bp < (int) BlackPawn) {
6103             if(j == BOARD_RGHT+1)
6104                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6105             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6106             if(message[0] == '+' || message[0] == '~') {
6107               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6108                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6109                         AAA + j, ONE + i);
6110             }
6111             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6112                 message[1] = BOARD_RGHT   - 1 - j + '1';
6113                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6114             }
6115             SendToProgram(message, cps);
6116           }
6117         }
6118       }
6119
6120       SendToProgram("c\n", cps);
6121       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6122         bp = &boards[moveNum][i][left];
6123         for (j = left; j < right; j++, bp++) {
6124           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6125           if (((int) *bp != (int) EmptySquare)
6126               && ((int) *bp >= (int) BlackPawn)) {
6127             if(j == BOARD_LEFT-2)
6128                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6129             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6130                     AAA + j, ONE + i);
6131             if(message[0] == '+' || message[0] == '~') {
6132               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6133                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6134                         AAA + j, ONE + i);
6135             }
6136             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6137                 message[1] = BOARD_RGHT   - 1 - j + '1';
6138                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6139             }
6140             SendToProgram(message, cps);
6141           }
6142         }
6143       }
6144
6145       SendToProgram(".\n", cps);
6146     }
6147     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6148 }
6149
6150 char exclusionHeader[MSG_SIZ];
6151 int exCnt, excludePtr;
6152 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6153 static Exclusion excluTab[200];
6154 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6155
6156 static void
6157 WriteMap (int s)
6158 {
6159     int j;
6160     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6161     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6162 }
6163
6164 static void
6165 ClearMap ()
6166 {
6167     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6168     excludePtr = 24; exCnt = 0;
6169     WriteMap(0);
6170 }
6171
6172 static void
6173 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6174 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6175     char buf[2*MOVE_LEN], *p;
6176     Exclusion *e = excluTab;
6177     int i;
6178     for(i=0; i<exCnt; i++)
6179         if(e[i].ff == fromX && e[i].fr == fromY &&
6180            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6181     if(i == exCnt) { // was not in exclude list; add it
6182         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6183         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6184             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6185             return; // abort
6186         }
6187         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6188         excludePtr++; e[i].mark = excludePtr++;
6189         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6190         exCnt++;
6191     }
6192     exclusionHeader[e[i].mark] = state;
6193 }
6194
6195 static int
6196 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6197 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6198     char buf[MSG_SIZ];
6199     int j, k;
6200     ChessMove moveType;
6201     if((signed char)promoChar == -1) { // kludge to indicate best move
6202         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6203             return 1; // if unparsable, abort
6204     }
6205     // update exclusion map (resolving toggle by consulting existing state)
6206     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6207     j = k%8; k >>= 3;
6208     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6209     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6210          excludeMap[k] |=   1<<j;
6211     else excludeMap[k] &= ~(1<<j);
6212     // update header
6213     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6214     // inform engine
6215     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6216     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6217     SendToProgram(buf, &first);
6218     return (state == '+');
6219 }
6220
6221 static void
6222 ExcludeClick (int index)
6223 {
6224     int i, j;
6225     Exclusion *e = excluTab;
6226     if(index < 25) { // none, best or tail clicked
6227         if(index < 13) { // none: include all
6228             WriteMap(0); // clear map
6229             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6230             SendToProgram("include all\n", &first); // and inform engine
6231         } else if(index > 18) { // tail
6232             if(exclusionHeader[19] == '-') { // tail was excluded
6233                 SendToProgram("include all\n", &first);
6234                 WriteMap(0); // clear map completely
6235                 // now re-exclude selected moves
6236                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6237                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6238             } else { // tail was included or in mixed state
6239                 SendToProgram("exclude all\n", &first);
6240                 WriteMap(0xFF); // fill map completely
6241                 // now re-include selected moves
6242                 j = 0; // count them
6243                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6244                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6245                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6246             }
6247         } else { // best
6248             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6249         }
6250     } else {
6251         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6252             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6253             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6254             break;
6255         }
6256     }
6257 }
6258
6259 ChessSquare
6260 DefaultPromoChoice (int white)
6261 {
6262     ChessSquare result;
6263     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6264         result = WhiteFerz; // no choice
6265     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6266         result= WhiteKing; // in Suicide Q is the last thing we want
6267     else if(gameInfo.variant == VariantSpartan)
6268         result = white ? WhiteQueen : WhiteAngel;
6269     else result = WhiteQueen;
6270     if(!white) result = WHITE_TO_BLACK result;
6271     return result;
6272 }
6273
6274 static int autoQueen; // [HGM] oneclick
6275
6276 int
6277 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6278 {
6279     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6280     /* [HGM] add Shogi promotions */
6281     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6282     ChessSquare piece;
6283     ChessMove moveType;
6284     Boolean premove;
6285
6286     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6287     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6288
6289     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6290       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6291         return FALSE;
6292
6293     piece = boards[currentMove][fromY][fromX];
6294     if(gameInfo.variant == VariantShogi) {
6295         promotionZoneSize = BOARD_HEIGHT/3;
6296         highestPromotingPiece = (int)WhiteFerz;
6297     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6298         promotionZoneSize = 3;
6299     }
6300
6301     // Treat Lance as Pawn when it is not representing Amazon
6302     if(gameInfo.variant != VariantSuper) {
6303         if(piece == WhiteLance) piece = WhitePawn; else
6304         if(piece == BlackLance) piece = BlackPawn;
6305     }
6306
6307     // next weed out all moves that do not touch the promotion zone at all
6308     if((int)piece >= BlackPawn) {
6309         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6310              return FALSE;
6311         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6312     } else {
6313         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6314            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6315     }
6316
6317     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6318
6319     // weed out mandatory Shogi promotions
6320     if(gameInfo.variant == VariantShogi) {
6321         if(piece >= BlackPawn) {
6322             if(toY == 0 && piece == BlackPawn ||
6323                toY == 0 && piece == BlackQueen ||
6324                toY <= 1 && piece == BlackKnight) {
6325                 *promoChoice = '+';
6326                 return FALSE;
6327             }
6328         } else {
6329             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6330                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6331                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6332                 *promoChoice = '+';
6333                 return FALSE;
6334             }
6335         }
6336     }
6337
6338     // weed out obviously illegal Pawn moves
6339     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6340         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6341         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6342         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6343         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6344         // note we are not allowed to test for valid (non-)capture, due to premove
6345     }
6346
6347     // we either have a choice what to promote to, or (in Shogi) whether to promote
6348     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6349         *promoChoice = PieceToChar(BlackFerz);  // no choice
6350         return FALSE;
6351     }
6352     // no sense asking what we must promote to if it is going to explode...
6353     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6354         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6355         return FALSE;
6356     }
6357     // give caller the default choice even if we will not make it
6358     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6359     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6360     if(        sweepSelect && gameInfo.variant != VariantGreat
6361                            && gameInfo.variant != VariantGrand
6362                            && gameInfo.variant != VariantSuper) return FALSE;
6363     if(autoQueen) return FALSE; // predetermined
6364
6365     // suppress promotion popup on illegal moves that are not premoves
6366     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6367               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6368     if(appData.testLegality && !premove) {
6369         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6370                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6371         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6372             return FALSE;
6373     }
6374
6375     return TRUE;
6376 }
6377
6378 int
6379 InPalace (int row, int column)
6380 {   /* [HGM] for Xiangqi */
6381     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6382          column < (BOARD_WIDTH + 4)/2 &&
6383          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6384     return FALSE;
6385 }
6386
6387 int
6388 PieceForSquare (int x, int y)
6389 {
6390   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6391      return -1;
6392   else
6393      return boards[currentMove][y][x];
6394 }
6395
6396 int
6397 OKToStartUserMove (int x, int y)
6398 {
6399     ChessSquare from_piece;
6400     int white_piece;
6401
6402     if (matchMode) return FALSE;
6403     if (gameMode == EditPosition) return TRUE;
6404
6405     if (x >= 0 && y >= 0)
6406       from_piece = boards[currentMove][y][x];
6407     else
6408       from_piece = EmptySquare;
6409
6410     if (from_piece == EmptySquare) return FALSE;
6411
6412     white_piece = (int)from_piece >= (int)WhitePawn &&
6413       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6414
6415     switch (gameMode) {
6416       case AnalyzeFile:
6417       case TwoMachinesPlay:
6418       case EndOfGame:
6419         return FALSE;
6420
6421       case IcsObserving:
6422       case IcsIdle:
6423         return FALSE;
6424
6425       case MachinePlaysWhite:
6426       case IcsPlayingBlack:
6427         if (appData.zippyPlay) return FALSE;
6428         if (white_piece) {
6429             DisplayMoveError(_("You are playing Black"));
6430             return FALSE;
6431         }
6432         break;
6433
6434       case MachinePlaysBlack:
6435       case IcsPlayingWhite:
6436         if (appData.zippyPlay) return FALSE;
6437         if (!white_piece) {
6438             DisplayMoveError(_("You are playing White"));
6439             return FALSE;
6440         }
6441         break;
6442
6443       case PlayFromGameFile:
6444             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6445       case EditGame:
6446         if (!white_piece && WhiteOnMove(currentMove)) {
6447             DisplayMoveError(_("It is White's turn"));
6448             return FALSE;
6449         }
6450         if (white_piece && !WhiteOnMove(currentMove)) {
6451             DisplayMoveError(_("It is Black's turn"));
6452             return FALSE;
6453         }
6454         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6455             /* Editing correspondence game history */
6456             /* Could disallow this or prompt for confirmation */
6457             cmailOldMove = -1;
6458         }
6459         break;
6460
6461       case BeginningOfGame:
6462         if (appData.icsActive) return FALSE;
6463         if (!appData.noChessProgram) {
6464             if (!white_piece) {
6465                 DisplayMoveError(_("You are playing White"));
6466                 return FALSE;
6467             }
6468         }
6469         break;
6470
6471       case Training:
6472         if (!white_piece && WhiteOnMove(currentMove)) {
6473             DisplayMoveError(_("It is White's turn"));
6474             return FALSE;
6475         }
6476         if (white_piece && !WhiteOnMove(currentMove)) {
6477             DisplayMoveError(_("It is Black's turn"));
6478             return FALSE;
6479         }
6480         break;
6481
6482       default:
6483       case IcsExamining:
6484         break;
6485     }
6486     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6487         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6488         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6489         && gameMode != AnalyzeFile && gameMode != Training) {
6490         DisplayMoveError(_("Displayed position is not current"));
6491         return FALSE;
6492     }
6493     return TRUE;
6494 }
6495
6496 Boolean
6497 OnlyMove (int *x, int *y, Boolean captures) 
6498 {
6499     DisambiguateClosure cl;
6500     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6501     switch(gameMode) {
6502       case MachinePlaysBlack:
6503       case IcsPlayingWhite:
6504       case BeginningOfGame:
6505         if(!WhiteOnMove(currentMove)) return FALSE;
6506         break;
6507       case MachinePlaysWhite:
6508       case IcsPlayingBlack:
6509         if(WhiteOnMove(currentMove)) return FALSE;
6510         break;
6511       case EditGame:
6512         break;
6513       default:
6514         return FALSE;
6515     }
6516     cl.pieceIn = EmptySquare;
6517     cl.rfIn = *y;
6518     cl.ffIn = *x;
6519     cl.rtIn = -1;
6520     cl.ftIn = -1;
6521     cl.promoCharIn = NULLCHAR;
6522     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6523     if( cl.kind == NormalMove ||
6524         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6525         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6526         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6527       fromX = cl.ff;
6528       fromY = cl.rf;
6529       *x = cl.ft;
6530       *y = cl.rt;
6531       return TRUE;
6532     }
6533     if(cl.kind != ImpossibleMove) return FALSE;
6534     cl.pieceIn = EmptySquare;
6535     cl.rfIn = -1;
6536     cl.ffIn = -1;
6537     cl.rtIn = *y;
6538     cl.ftIn = *x;
6539     cl.promoCharIn = NULLCHAR;
6540     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6541     if( cl.kind == NormalMove ||
6542         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6543         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6544         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6545       fromX = cl.ff;
6546       fromY = cl.rf;
6547       *x = cl.ft;
6548       *y = cl.rt;
6549       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6550       return TRUE;
6551     }
6552     return FALSE;
6553 }
6554
6555 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6556 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6557 int lastLoadGameUseList = FALSE;
6558 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6559 ChessMove lastLoadGameStart = EndOfFile;
6560 int doubleClick;
6561
6562 void
6563 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6564 {
6565     ChessMove moveType;
6566     ChessSquare pup;
6567     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6568
6569     /* Check if the user is playing in turn.  This is complicated because we
6570        let the user "pick up" a piece before it is his turn.  So the piece he
6571        tried to pick up may have been captured by the time he puts it down!
6572        Therefore we use the color the user is supposed to be playing in this
6573        test, not the color of the piece that is currently on the starting
6574        square---except in EditGame mode, where the user is playing both
6575        sides; fortunately there the capture race can't happen.  (It can
6576        now happen in IcsExamining mode, but that's just too bad.  The user
6577        will get a somewhat confusing message in that case.)
6578        */
6579
6580     switch (gameMode) {
6581       case AnalyzeFile:
6582       case TwoMachinesPlay:
6583       case EndOfGame:
6584       case IcsObserving:
6585       case IcsIdle:
6586         /* We switched into a game mode where moves are not accepted,
6587            perhaps while the mouse button was down. */
6588         return;
6589
6590       case MachinePlaysWhite:
6591         /* User is moving for Black */
6592         if (WhiteOnMove(currentMove)) {
6593             DisplayMoveError(_("It is White's turn"));
6594             return;
6595         }
6596         break;
6597
6598       case MachinePlaysBlack:
6599         /* User is moving for White */
6600         if (!WhiteOnMove(currentMove)) {
6601             DisplayMoveError(_("It is Black's turn"));
6602             return;
6603         }
6604         break;
6605
6606       case PlayFromGameFile:
6607             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6608       case EditGame:
6609       case IcsExamining:
6610       case BeginningOfGame:
6611       case AnalyzeMode:
6612       case Training:
6613         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6614         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6615             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6616             /* User is moving for Black */
6617             if (WhiteOnMove(currentMove)) {
6618                 DisplayMoveError(_("It is White's turn"));
6619                 return;
6620             }
6621         } else {
6622             /* User is moving for White */
6623             if (!WhiteOnMove(currentMove)) {
6624                 DisplayMoveError(_("It is Black's turn"));
6625                 return;
6626             }
6627         }
6628         break;
6629
6630       case IcsPlayingBlack:
6631         /* User is moving for Black */
6632         if (WhiteOnMove(currentMove)) {
6633             if (!appData.premove) {
6634                 DisplayMoveError(_("It is White's turn"));
6635             } else if (toX >= 0 && toY >= 0) {
6636                 premoveToX = toX;
6637                 premoveToY = toY;
6638                 premoveFromX = fromX;
6639                 premoveFromY = fromY;
6640                 premovePromoChar = promoChar;
6641                 gotPremove = 1;
6642                 if (appData.debugMode)
6643                     fprintf(debugFP, "Got premove: fromX %d,"
6644                             "fromY %d, toX %d, toY %d\n",
6645                             fromX, fromY, toX, toY);
6646             }
6647             return;
6648         }
6649         break;
6650
6651       case IcsPlayingWhite:
6652         /* User is moving for White */
6653         if (!WhiteOnMove(currentMove)) {
6654             if (!appData.premove) {
6655                 DisplayMoveError(_("It is Black's turn"));
6656             } else if (toX >= 0 && toY >= 0) {
6657                 premoveToX = toX;
6658                 premoveToY = toY;
6659                 premoveFromX = fromX;
6660                 premoveFromY = fromY;
6661                 premovePromoChar = promoChar;
6662                 gotPremove = 1;
6663                 if (appData.debugMode)
6664                     fprintf(debugFP, "Got premove: fromX %d,"
6665                             "fromY %d, toX %d, toY %d\n",
6666                             fromX, fromY, toX, toY);
6667             }
6668             return;
6669         }
6670         break;
6671
6672       default:
6673         break;
6674
6675       case EditPosition:
6676         /* EditPosition, empty square, or different color piece;
6677            click-click move is possible */
6678         if (toX == -2 || toY == -2) {
6679             boards[0][fromY][fromX] = EmptySquare;
6680             DrawPosition(FALSE, boards[currentMove]);
6681             return;
6682         } else if (toX >= 0 && toY >= 0) {
6683             boards[0][toY][toX] = boards[0][fromY][fromX];
6684             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6685                 if(boards[0][fromY][0] != EmptySquare) {
6686                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6687                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6688                 }
6689             } else
6690             if(fromX == BOARD_RGHT+1) {
6691                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6692                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6693                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6694                 }
6695             } else
6696             boards[0][fromY][fromX] = gatingPiece;
6697             DrawPosition(FALSE, boards[currentMove]);
6698             return;
6699         }
6700         return;
6701     }
6702
6703     if(toX < 0 || toY < 0) return;
6704     pup = boards[currentMove][toY][toX];
6705
6706     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6707     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6708          if( pup != EmptySquare ) return;
6709          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6710            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6711                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6712            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6713            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6714            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6715            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6716          fromY = DROP_RANK;
6717     }
6718
6719     /* [HGM] always test for legality, to get promotion info */
6720     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6721                                          fromY, fromX, toY, toX, promoChar);
6722
6723     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6724
6725     /* [HGM] but possibly ignore an IllegalMove result */
6726     if (appData.testLegality) {
6727         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6728             DisplayMoveError(_("Illegal move"));
6729             return;
6730         }
6731     }
6732
6733     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6734         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6735              ClearPremoveHighlights(); // was included
6736         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6737         return;
6738     }
6739
6740     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6741 }
6742
6743 /* Common tail of UserMoveEvent and DropMenuEvent */
6744 int
6745 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6746 {
6747     char *bookHit = 0;
6748
6749     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6750         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6751         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6752         if(WhiteOnMove(currentMove)) {
6753             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6754         } else {
6755             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6756         }
6757     }
6758
6759     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6760        move type in caller when we know the move is a legal promotion */
6761     if(moveType == NormalMove && promoChar)
6762         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6763
6764     /* [HGM] <popupFix> The following if has been moved here from
6765        UserMoveEvent(). Because it seemed to belong here (why not allow
6766        piece drops in training games?), and because it can only be
6767        performed after it is known to what we promote. */
6768     if (gameMode == Training) {
6769       /* compare the move played on the board to the next move in the
6770        * game. If they match, display the move and the opponent's response.
6771        * If they don't match, display an error message.
6772        */
6773       int saveAnimate;
6774       Board testBoard;
6775       CopyBoard(testBoard, boards[currentMove]);
6776       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6777
6778       if (CompareBoards(testBoard, boards[currentMove+1])) {
6779         ForwardInner(currentMove+1);
6780
6781         /* Autoplay the opponent's response.
6782          * if appData.animate was TRUE when Training mode was entered,
6783          * the response will be animated.
6784          */
6785         saveAnimate = appData.animate;
6786         appData.animate = animateTraining;
6787         ForwardInner(currentMove+1);
6788         appData.animate = saveAnimate;
6789
6790         /* check for the end of the game */
6791         if (currentMove >= forwardMostMove) {
6792           gameMode = PlayFromGameFile;
6793           ModeHighlight();
6794           SetTrainingModeOff();
6795           DisplayInformation(_("End of game"));
6796         }
6797       } else {
6798         DisplayError(_("Incorrect move"), 0);
6799       }
6800       return 1;
6801     }
6802
6803   /* Ok, now we know that the move is good, so we can kill
6804      the previous line in Analysis Mode */
6805   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6806                                 && currentMove < forwardMostMove) {
6807     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6808     else forwardMostMove = currentMove;
6809   }
6810
6811   ClearMap();
6812
6813   /* If we need the chess program but it's dead, restart it */
6814   ResurrectChessProgram();
6815
6816   /* A user move restarts a paused game*/
6817   if (pausing)
6818     PauseEvent();
6819
6820   thinkOutput[0] = NULLCHAR;
6821
6822   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6823
6824   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6825     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6826     return 1;
6827   }
6828
6829   if (gameMode == BeginningOfGame) {
6830     if (appData.noChessProgram) {
6831       gameMode = EditGame;
6832       SetGameInfo();
6833     } else {
6834       char buf[MSG_SIZ];
6835       gameMode = MachinePlaysBlack;
6836       StartClocks();
6837       SetGameInfo();
6838       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6839       DisplayTitle(buf);
6840       if (first.sendName) {
6841         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6842         SendToProgram(buf, &first);
6843       }
6844       StartClocks();
6845     }
6846     ModeHighlight();
6847   }
6848
6849   /* Relay move to ICS or chess engine */
6850   if (appData.icsActive) {
6851     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6852         gameMode == IcsExamining) {
6853       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6854         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6855         SendToICS("draw ");
6856         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6857       }
6858       // also send plain move, in case ICS does not understand atomic claims
6859       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6860       ics_user_moved = 1;
6861     }
6862   } else {
6863     if (first.sendTime && (gameMode == BeginningOfGame ||
6864                            gameMode == MachinePlaysWhite ||
6865                            gameMode == MachinePlaysBlack)) {
6866       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6867     }
6868     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6869          // [HGM] book: if program might be playing, let it use book
6870         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6871         first.maybeThinking = TRUE;
6872     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6873         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6874         SendBoard(&first, currentMove+1);
6875     } else SendMoveToProgram(forwardMostMove-1, &first);
6876     if (currentMove == cmailOldMove + 1) {
6877       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6878     }
6879   }
6880
6881   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6882
6883   switch (gameMode) {
6884   case EditGame:
6885     if(appData.testLegality)
6886     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6887     case MT_NONE:
6888     case MT_CHECK:
6889       break;
6890     case MT_CHECKMATE:
6891     case MT_STAINMATE:
6892       if (WhiteOnMove(currentMove)) {
6893         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6894       } else {
6895         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6896       }
6897       break;
6898     case MT_STALEMATE:
6899       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6900       break;
6901     }
6902     break;
6903
6904   case MachinePlaysBlack:
6905   case MachinePlaysWhite:
6906     /* disable certain menu options while machine is thinking */
6907     SetMachineThinkingEnables();
6908     break;
6909
6910   default:
6911     break;
6912   }
6913
6914   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6915   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6916
6917   if(bookHit) { // [HGM] book: simulate book reply
6918         static char bookMove[MSG_SIZ]; // a bit generous?
6919
6920         programStats.nodes = programStats.depth = programStats.time =
6921         programStats.score = programStats.got_only_move = 0;
6922         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6923
6924         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6925         strcat(bookMove, bookHit);
6926         HandleMachineMove(bookMove, &first);
6927   }
6928   return 1;
6929 }
6930
6931 void
6932 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6933 {
6934     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6935     Markers *m = (Markers *) closure;
6936     if(rf == fromY && ff == fromX)
6937         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6938                          || kind == WhiteCapturesEnPassant
6939                          || kind == BlackCapturesEnPassant);
6940     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6941 }
6942
6943 void
6944 MarkTargetSquares (int clear)
6945 {
6946   int x, y;
6947   if(clear) // no reason to ever suppress clearing
6948     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6949   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6950      !appData.testLegality || gameMode == EditPosition) return;
6951   if(!clear) {
6952     int capt = 0;
6953     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6954     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6955       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6956       if(capt)
6957       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6958     }
6959   }
6960   DrawPosition(FALSE, NULL);
6961 }
6962
6963 int
6964 Explode (Board board, int fromX, int fromY, int toX, int toY)
6965 {
6966     if(gameInfo.variant == VariantAtomic &&
6967        (board[toY][toX] != EmptySquare ||                     // capture?
6968         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6969                          board[fromY][fromX] == BlackPawn   )
6970       )) {
6971         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6972         return TRUE;
6973     }
6974     return FALSE;
6975 }
6976
6977 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6978
6979 int
6980 CanPromote (ChessSquare piece, int y)
6981 {
6982         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6983         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6984         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6985            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6986            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6987                                                   gameInfo.variant == VariantMakruk) return FALSE;
6988         return (piece == BlackPawn && y == 1 ||
6989                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6990                 piece == BlackLance && y == 1 ||
6991                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6992 }
6993
6994 void
6995 LeftClick (ClickType clickType, int xPix, int yPix)
6996 {
6997     int x, y;
6998     Boolean saveAnimate;
6999     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7000     char promoChoice = NULLCHAR;
7001     ChessSquare piece;
7002     static TimeMark lastClickTime, prevClickTime;
7003
7004     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7005
7006     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7007
7008     if (clickType == Press) ErrorPopDown();
7009
7010     x = EventToSquare(xPix, BOARD_WIDTH);
7011     y = EventToSquare(yPix, BOARD_HEIGHT);
7012     if (!flipView && y >= 0) {
7013         y = BOARD_HEIGHT - 1 - y;
7014     }
7015     if (flipView && x >= 0) {
7016         x = BOARD_WIDTH - 1 - x;
7017     }
7018
7019     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7020         defaultPromoChoice = promoSweep;
7021         promoSweep = EmptySquare;   // terminate sweep
7022         promoDefaultAltered = TRUE;
7023         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7024     }
7025
7026     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7027         if(clickType == Release) return; // ignore upclick of click-click destination
7028         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7029         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7030         if(gameInfo.holdingsWidth &&
7031                 (WhiteOnMove(currentMove)
7032                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7033                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7034             // click in right holdings, for determining promotion piece
7035             ChessSquare p = boards[currentMove][y][x];
7036             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7037             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7038             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7039                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7040                 fromX = fromY = -1;
7041                 return;
7042             }
7043         }
7044         DrawPosition(FALSE, boards[currentMove]);
7045         return;
7046     }
7047
7048     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7049     if(clickType == Press
7050             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7051               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7052               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7053         return;
7054
7055     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7056         // could be static click on premove from-square: abort premove
7057         gotPremove = 0;
7058         ClearPremoveHighlights();
7059     }
7060
7061     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7062         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7063
7064     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7065         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7066                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7067         defaultPromoChoice = DefaultPromoChoice(side);
7068     }
7069
7070     autoQueen = appData.alwaysPromoteToQueen;
7071
7072     if (fromX == -1) {
7073       int originalY = y;
7074       gatingPiece = EmptySquare;
7075       if (clickType != Press) {
7076         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7077             DragPieceEnd(xPix, yPix); dragging = 0;
7078             DrawPosition(FALSE, NULL);
7079         }
7080         return;
7081       }
7082       doubleClick = FALSE;
7083       fromX = x; fromY = y; toX = toY = -1;
7084       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7085          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7086          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7087             /* First square */
7088             if (OKToStartUserMove(fromX, fromY)) {
7089                 second = 0;
7090                 MarkTargetSquares(0);
7091                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7092                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7093                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7094                     promoSweep = defaultPromoChoice;
7095                     selectFlag = 0; lastX = xPix; lastY = yPix;
7096                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7097                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7098                 }
7099                 if (appData.highlightDragging) {
7100                     SetHighlights(fromX, fromY, -1, -1);
7101                 } else {
7102                     ClearHighlights();
7103                 }
7104             } else fromX = fromY = -1;
7105             return;
7106         }
7107     }
7108
7109     /* fromX != -1 */
7110     if (clickType == Press && gameMode != EditPosition) {
7111         ChessSquare fromP;
7112         ChessSquare toP;
7113         int frc;
7114
7115         // ignore off-board to clicks
7116         if(y < 0 || x < 0) return;
7117
7118         /* Check if clicking again on the same color piece */
7119         fromP = boards[currentMove][fromY][fromX];
7120         toP = boards[currentMove][y][x];
7121         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7122         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7123              WhitePawn <= toP && toP <= WhiteKing &&
7124              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7125              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7126             (BlackPawn <= fromP && fromP <= BlackKing &&
7127              BlackPawn <= toP && toP <= BlackKing &&
7128              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7129              !(fromP == BlackKing && toP == BlackRook && frc))) {
7130             /* Clicked again on same color piece -- changed his mind */
7131             second = (x == fromX && y == fromY);
7132             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7133                 second = FALSE; // first double-click rather than scond click
7134                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7135             }
7136             promoDefaultAltered = FALSE;
7137             MarkTargetSquares(1);
7138            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7139             if (appData.highlightDragging) {
7140                 SetHighlights(x, y, -1, -1);
7141             } else {
7142                 ClearHighlights();
7143             }
7144             if (OKToStartUserMove(x, y)) {
7145                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7146                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7147                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7148                  gatingPiece = boards[currentMove][fromY][fromX];
7149                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7150                 fromX = x;
7151                 fromY = y; dragging = 1;
7152                 MarkTargetSquares(0);
7153                 DragPieceBegin(xPix, yPix, FALSE);
7154                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7155                     promoSweep = defaultPromoChoice;
7156                     selectFlag = 0; lastX = xPix; lastY = yPix;
7157                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7158                 }
7159             }
7160            }
7161            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7162            second = FALSE; 
7163         }
7164         // ignore clicks on holdings
7165         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7166     }
7167
7168     if (clickType == Release && x == fromX && y == fromY) {
7169         DragPieceEnd(xPix, yPix); dragging = 0;
7170         if(clearFlag) {
7171             // a deferred attempt to click-click move an empty square on top of a piece
7172             boards[currentMove][y][x] = EmptySquare;
7173             ClearHighlights();
7174             DrawPosition(FALSE, boards[currentMove]);
7175             fromX = fromY = -1; clearFlag = 0;
7176             return;
7177         }
7178         if (appData.animateDragging) {
7179             /* Undo animation damage if any */
7180             DrawPosition(FALSE, NULL);
7181         }
7182         if (second || sweepSelecting) {
7183             /* Second up/down in same square; just abort move */
7184             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7185             second = sweepSelecting = 0;
7186             fromX = fromY = -1;
7187             gatingPiece = EmptySquare;
7188             ClearHighlights();
7189             gotPremove = 0;
7190             ClearPremoveHighlights();
7191         } else {
7192             /* First upclick in same square; start click-click mode */
7193             SetHighlights(x, y, -1, -1);
7194         }
7195         return;
7196     }
7197
7198     clearFlag = 0;
7199
7200     /* we now have a different from- and (possibly off-board) to-square */
7201     /* Completed move */
7202     if(!sweepSelecting) {
7203         toX = x;
7204         toY = y;
7205     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7206
7207     saveAnimate = appData.animate;
7208     if (clickType == Press) {
7209         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7210             // must be Edit Position mode with empty-square selected
7211             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7212             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7213             return;
7214         }
7215         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7216           if(appData.sweepSelect) {
7217             ChessSquare piece = boards[currentMove][fromY][fromX];
7218             promoSweep = defaultPromoChoice;
7219             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7220             selectFlag = 0; lastX = xPix; lastY = yPix;
7221             Sweep(0); // Pawn that is going to promote: preview promotion piece
7222             sweepSelecting = 1;
7223             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7224             MarkTargetSquares(1);
7225           }
7226           return; // promo popup appears on up-click
7227         }
7228         /* Finish clickclick move */
7229         if (appData.animate || appData.highlightLastMove) {
7230             SetHighlights(fromX, fromY, toX, toY);
7231         } else {
7232             ClearHighlights();
7233         }
7234     } else {
7235         /* Finish drag move */
7236         if (appData.highlightLastMove) {
7237             SetHighlights(fromX, fromY, toX, toY);
7238         } else {
7239             ClearHighlights();
7240         }
7241         DragPieceEnd(xPix, yPix); dragging = 0;
7242         /* Don't animate move and drag both */
7243         appData.animate = FALSE;
7244     }
7245     MarkTargetSquares(1);
7246
7247     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7248     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7249         ChessSquare piece = boards[currentMove][fromY][fromX];
7250         if(gameMode == EditPosition && piece != EmptySquare &&
7251            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7252             int n;
7253
7254             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7255                 n = PieceToNumber(piece - (int)BlackPawn);
7256                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7257                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7258                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7259             } else
7260             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7261                 n = PieceToNumber(piece);
7262                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7263                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7264                 boards[currentMove][n][BOARD_WIDTH-2]++;
7265             }
7266             boards[currentMove][fromY][fromX] = EmptySquare;
7267         }
7268         ClearHighlights();
7269         fromX = fromY = -1;
7270         DrawPosition(TRUE, boards[currentMove]);
7271         return;
7272     }
7273
7274     // off-board moves should not be highlighted
7275     if(x < 0 || y < 0) ClearHighlights();
7276
7277     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7278
7279     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7280         SetHighlights(fromX, fromY, toX, toY);
7281         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7282             // [HGM] super: promotion to captured piece selected from holdings
7283             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7284             promotionChoice = TRUE;
7285             // kludge follows to temporarily execute move on display, without promoting yet
7286             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7287             boards[currentMove][toY][toX] = p;
7288             DrawPosition(FALSE, boards[currentMove]);
7289             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7290             boards[currentMove][toY][toX] = q;
7291             DisplayMessage("Click in holdings to choose piece", "");
7292             return;
7293         }
7294         PromotionPopUp();
7295     } else {
7296         int oldMove = currentMove;
7297         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7298         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7299         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7300         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7301            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7302             DrawPosition(TRUE, boards[currentMove]);
7303         fromX = fromY = -1;
7304     }
7305     appData.animate = saveAnimate;
7306     if (appData.animate || appData.animateDragging) {
7307         /* Undo animation damage if needed */
7308         DrawPosition(FALSE, NULL);
7309     }
7310 }
7311
7312 int
7313 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7314 {   // front-end-free part taken out of PieceMenuPopup
7315     int whichMenu; int xSqr, ySqr;
7316
7317     if(seekGraphUp) { // [HGM] seekgraph
7318         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7319         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7320         return -2;
7321     }
7322
7323     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7324          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7325         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7326         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7327         if(action == Press)   {
7328             originalFlip = flipView;
7329             flipView = !flipView; // temporarily flip board to see game from partners perspective
7330             DrawPosition(TRUE, partnerBoard);
7331             DisplayMessage(partnerStatus, "");
7332             partnerUp = TRUE;
7333         } else if(action == Release) {
7334             flipView = originalFlip;
7335             DrawPosition(TRUE, boards[currentMove]);
7336             partnerUp = FALSE;
7337         }
7338         return -2;
7339     }
7340
7341     xSqr = EventToSquare(x, BOARD_WIDTH);
7342     ySqr = EventToSquare(y, BOARD_HEIGHT);
7343     if (action == Release) {
7344         if(pieceSweep != EmptySquare) {
7345             EditPositionMenuEvent(pieceSweep, toX, toY);
7346             pieceSweep = EmptySquare;
7347         } else UnLoadPV(); // [HGM] pv
7348     }
7349     if (action != Press) return -2; // return code to be ignored
7350     switch (gameMode) {
7351       case IcsExamining:
7352         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7353       case EditPosition:
7354         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7355         if (xSqr < 0 || ySqr < 0) return -1;
7356         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7357         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7358         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7359         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7360         NextPiece(0);
7361         return 2; // grab
7362       case IcsObserving:
7363         if(!appData.icsEngineAnalyze) return -1;
7364       case IcsPlayingWhite:
7365       case IcsPlayingBlack:
7366         if(!appData.zippyPlay) goto noZip;
7367       case AnalyzeMode:
7368       case AnalyzeFile:
7369       case MachinePlaysWhite:
7370       case MachinePlaysBlack:
7371       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7372         if (!appData.dropMenu) {
7373           LoadPV(x, y);
7374           return 2; // flag front-end to grab mouse events
7375         }
7376         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7377            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7378       case EditGame:
7379       noZip:
7380         if (xSqr < 0 || ySqr < 0) return -1;
7381         if (!appData.dropMenu || appData.testLegality &&
7382             gameInfo.variant != VariantBughouse &&
7383             gameInfo.variant != VariantCrazyhouse) return -1;
7384         whichMenu = 1; // drop menu
7385         break;
7386       default:
7387         return -1;
7388     }
7389
7390     if (((*fromX = xSqr) < 0) ||
7391         ((*fromY = ySqr) < 0)) {
7392         *fromX = *fromY = -1;
7393         return -1;
7394     }
7395     if (flipView)
7396       *fromX = BOARD_WIDTH - 1 - *fromX;
7397     else
7398       *fromY = BOARD_HEIGHT - 1 - *fromY;
7399
7400     return whichMenu;
7401 }
7402
7403 void
7404 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7405 {
7406 //    char * hint = lastHint;
7407     FrontEndProgramStats stats;
7408
7409     stats.which = cps == &first ? 0 : 1;
7410     stats.depth = cpstats->depth;
7411     stats.nodes = cpstats->nodes;
7412     stats.score = cpstats->score;
7413     stats.time = cpstats->time;
7414     stats.pv = cpstats->movelist;
7415     stats.hint = lastHint;
7416     stats.an_move_index = 0;
7417     stats.an_move_count = 0;
7418
7419     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7420         stats.hint = cpstats->move_name;
7421         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7422         stats.an_move_count = cpstats->nr_moves;
7423     }
7424
7425     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
7426
7427     SetProgramStats( &stats );
7428 }
7429
7430 void
7431 ClearEngineOutputPane (int which)
7432 {
7433     static FrontEndProgramStats dummyStats;
7434     dummyStats.which = which;
7435     dummyStats.pv = "#";
7436     SetProgramStats( &dummyStats );
7437 }
7438
7439 #define MAXPLAYERS 500
7440
7441 char *
7442 TourneyStandings (int display)
7443 {
7444     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7445     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7446     char result, *p, *names[MAXPLAYERS];
7447
7448     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7449         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7450     names[0] = p = strdup(appData.participants);
7451     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7452
7453     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7454
7455     while(result = appData.results[nr]) {
7456         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7457         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7458         wScore = bScore = 0;
7459         switch(result) {
7460           case '+': wScore = 2; break;
7461           case '-': bScore = 2; break;
7462           case '=': wScore = bScore = 1; break;
7463           case ' ':
7464           case '*': return strdup("busy"); // tourney not finished
7465         }
7466         score[w] += wScore;
7467         score[b] += bScore;
7468         games[w]++;
7469         games[b]++;
7470         nr++;
7471     }
7472     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7473     for(w=0; w<nPlayers; w++) {
7474         bScore = -1;
7475         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7476         ranking[w] = b; points[w] = bScore; score[b] = -2;
7477     }
7478     p = malloc(nPlayers*34+1);
7479     for(w=0; w<nPlayers && w<display; w++)
7480         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7481     free(names[0]);
7482     return p;
7483 }
7484
7485 void
7486 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7487 {       // count all piece types
7488         int p, f, r;
7489         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7490         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7491         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7492                 p = board[r][f];
7493                 pCnt[p]++;
7494                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7495                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7496                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7497                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7498                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7499                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7500         }
7501 }
7502
7503 int
7504 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7505 {
7506         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7507         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7508
7509         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7510         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7511         if(myPawns == 2 && nMine == 3) // KPP
7512             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7513         if(myPawns == 1 && nMine == 2) // KP
7514             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7515         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7516             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7517         if(myPawns) return FALSE;
7518         if(pCnt[WhiteRook+side])
7519             return pCnt[BlackRook-side] ||
7520                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7521                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7522                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7523         if(pCnt[WhiteCannon+side]) {
7524             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7525             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7526         }
7527         if(pCnt[WhiteKnight+side])
7528             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7529         return FALSE;
7530 }
7531
7532 int
7533 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7534 {
7535         VariantClass v = gameInfo.variant;
7536
7537         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7538         if(v == VariantShatranj) return TRUE; // always winnable through baring
7539         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7540         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7541
7542         if(v == VariantXiangqi) {
7543                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7544
7545                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7546                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7547                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7548                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7549                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7550                 if(stale) // we have at least one last-rank P plus perhaps C
7551                     return majors // KPKX
7552                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7553                 else // KCA*E*
7554                     return pCnt[WhiteFerz+side] // KCAK
7555                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7556                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7557                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7558
7559         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7560                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7561
7562                 if(nMine == 1) return FALSE; // bare King
7563                 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
7564                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7565                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7566                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7567                 if(pCnt[WhiteKnight+side])
7568                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7569                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7570                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7571                 if(nBishops)
7572                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7573                 if(pCnt[WhiteAlfil+side])
7574                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7575                 if(pCnt[WhiteWazir+side])
7576                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7577         }
7578
7579         return TRUE;
7580 }
7581
7582 int
7583 CompareWithRights (Board b1, Board b2)
7584 {
7585     int rights = 0;
7586     if(!CompareBoards(b1, b2)) return FALSE;
7587     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7588     /* compare castling rights */
7589     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7590            rights++; /* King lost rights, while rook still had them */
7591     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7592         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7593            rights++; /* but at least one rook lost them */
7594     }
7595     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7596            rights++;
7597     if( b1[CASTLING][5] != NoRights ) {
7598         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7599            rights++;
7600     }
7601     return rights == 0;
7602 }
7603
7604 int
7605 Adjudicate (ChessProgramState *cps)
7606 {       // [HGM] some adjudications useful with buggy engines
7607         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7608         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7609         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7610         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7611         int k, count = 0; static int bare = 1;
7612         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7613         Boolean canAdjudicate = !appData.icsActive;
7614
7615         // most tests only when we understand the game, i.e. legality-checking on
7616             if( appData.testLegality )
7617             {   /* [HGM] Some more adjudications for obstinate engines */
7618                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7619                 static int moveCount = 6;
7620                 ChessMove result;
7621                 char *reason = NULL;
7622
7623                 /* Count what is on board. */
7624                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7625
7626                 /* Some material-based adjudications that have to be made before stalemate test */
7627                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7628                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7629                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7630                      if(canAdjudicate && appData.checkMates) {
7631                          if(engineOpponent)
7632                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7633                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7634                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7635                          return 1;
7636                      }
7637                 }
7638
7639                 /* Bare King in Shatranj (loses) or Losers (wins) */
7640                 if( nrW == 1 || nrB == 1) {
7641                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7642                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7643                      if(canAdjudicate && appData.checkMates) {
7644                          if(engineOpponent)
7645                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7646                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7647                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7648                          return 1;
7649                      }
7650                   } else
7651                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7652                   {    /* bare King */
7653                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7654                         if(canAdjudicate && appData.checkMates) {
7655                             /* but only adjudicate if adjudication enabled */
7656                             if(engineOpponent)
7657                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7658                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7659                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7660                             return 1;
7661                         }
7662                   }
7663                 } else bare = 1;
7664
7665
7666             // don't wait for engine to announce game end if we can judge ourselves
7667             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7668               case MT_CHECK:
7669                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7670                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7671                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7672                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7673                             checkCnt++;
7674                         if(checkCnt >= 2) {
7675                             reason = "Xboard adjudication: 3rd check";
7676                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7677                             break;
7678                         }
7679                     }
7680                 }
7681               case MT_NONE:
7682               default:
7683                 break;
7684               case MT_STALEMATE:
7685               case MT_STAINMATE:
7686                 reason = "Xboard adjudication: Stalemate";
7687                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7688                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7689                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7690                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7691                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7692                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7693                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7694                                                                         EP_CHECKMATE : EP_WINS);
7695                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7696                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7697                 }
7698                 break;
7699               case MT_CHECKMATE:
7700                 reason = "Xboard adjudication: Checkmate";
7701                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7702                 break;
7703             }
7704
7705                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7706                     case EP_STALEMATE:
7707                         result = GameIsDrawn; break;
7708                     case EP_CHECKMATE:
7709                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7710                     case EP_WINS:
7711                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7712                     default:
7713                         result = EndOfFile;
7714                 }
7715                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7716                     if(engineOpponent)
7717                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7718                     GameEnds( result, reason, GE_XBOARD );
7719                     return 1;
7720                 }
7721
7722                 /* Next absolutely insufficient mating material. */
7723                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7724                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7725                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7726
7727                      /* always flag draws, for judging claims */
7728                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7729
7730                      if(canAdjudicate && appData.materialDraws) {
7731                          /* but only adjudicate them if adjudication enabled */
7732                          if(engineOpponent) {
7733                            SendToProgram("force\n", engineOpponent); // suppress reply
7734                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7735                          }
7736                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7737                          return 1;
7738                      }
7739                 }
7740
7741                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7742                 if(gameInfo.variant == VariantXiangqi ?
7743                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7744                  : nrW + nrB == 4 &&
7745                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7746                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7747                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7748                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7749                    ) ) {
7750                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7751                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7752                           if(engineOpponent) {
7753                             SendToProgram("force\n", engineOpponent); // suppress reply
7754                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7755                           }
7756                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7757                           return 1;
7758                      }
7759                 } else moveCount = 6;
7760             }
7761
7762         // Repetition draws and 50-move rule can be applied independently of legality testing
7763
7764                 /* Check for rep-draws */
7765                 count = 0;
7766                 for(k = forwardMostMove-2;
7767                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7768                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7769                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7770                     k-=2)
7771                 {   int rights=0;
7772                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7773                         /* compare castling rights */
7774                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7775                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7776                                 rights++; /* King lost rights, while rook still had them */
7777                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7778                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7779                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7780                                    rights++; /* but at least one rook lost them */
7781                         }
7782                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7783                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7784                                 rights++;
7785                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7786                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7787                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7788                                    rights++;
7789                         }
7790                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7791                             && appData.drawRepeats > 1) {
7792                              /* adjudicate after user-specified nr of repeats */
7793                              int result = GameIsDrawn;
7794                              char *details = "XBoard adjudication: repetition draw";
7795                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7796                                 // [HGM] xiangqi: check for forbidden perpetuals
7797                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7798                                 for(m=forwardMostMove; m>k; m-=2) {
7799                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7800                                         ourPerpetual = 0; // the current mover did not always check
7801                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7802                                         hisPerpetual = 0; // the opponent did not always check
7803                                 }
7804                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7805                                                                         ourPerpetual, hisPerpetual);
7806                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7807                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7808                                     details = "Xboard adjudication: perpetual checking";
7809                                 } else
7810                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7811                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7812                                 } else
7813                                 // Now check for perpetual chases
7814                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7815                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7816                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7817                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7818                                         static char resdet[MSG_SIZ];
7819                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7820                                         details = resdet;
7821                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7822                                     } else
7823                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7824                                         break; // Abort repetition-checking loop.
7825                                 }
7826                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7827                              }
7828                              if(engineOpponent) {
7829                                SendToProgram("force\n", engineOpponent); // suppress reply
7830                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7831                              }
7832                              GameEnds( result, details, GE_XBOARD );
7833                              return 1;
7834                         }
7835                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7836                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7837                     }
7838                 }
7839
7840                 /* Now we test for 50-move draws. Determine ply count */
7841                 count = forwardMostMove;
7842                 /* look for last irreversble move */
7843                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7844                     count--;
7845                 /* if we hit starting position, add initial plies */
7846                 if( count == backwardMostMove )
7847                     count -= initialRulePlies;
7848                 count = forwardMostMove - count;
7849                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7850                         // adjust reversible move counter for checks in Xiangqi
7851                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7852                         if(i < backwardMostMove) i = backwardMostMove;
7853                         while(i <= forwardMostMove) {
7854                                 lastCheck = inCheck; // check evasion does not count
7855                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7856                                 if(inCheck || lastCheck) count--; // check does not count
7857                                 i++;
7858                         }
7859                 }
7860                 if( count >= 100)
7861                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7862                          /* this is used to judge if draw claims are legal */
7863                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7864                          if(engineOpponent) {
7865                            SendToProgram("force\n", engineOpponent); // suppress reply
7866                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7867                          }
7868                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7869                          return 1;
7870                 }
7871
7872                 /* if draw offer is pending, treat it as a draw claim
7873                  * when draw condition present, to allow engines a way to
7874                  * claim draws before making their move to avoid a race
7875                  * condition occurring after their move
7876                  */
7877                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7878                          char *p = NULL;
7879                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7880                              p = "Draw claim: 50-move rule";
7881                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7882                              p = "Draw claim: 3-fold repetition";
7883                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7884                              p = "Draw claim: insufficient mating material";
7885                          if( p != NULL && canAdjudicate) {
7886                              if(engineOpponent) {
7887                                SendToProgram("force\n", engineOpponent); // suppress reply
7888                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7889                              }
7890                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7891                              return 1;
7892                          }
7893                 }
7894
7895                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7896                     if(engineOpponent) {
7897                       SendToProgram("force\n", engineOpponent); // suppress reply
7898                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7899                     }
7900                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7901                     return 1;
7902                 }
7903         return 0;
7904 }
7905
7906 char *
7907 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7908 {   // [HGM] book: this routine intercepts moves to simulate book replies
7909     char *bookHit = NULL;
7910
7911     //first determine if the incoming move brings opponent into his book
7912     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7913         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7914     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7915     if(bookHit != NULL && !cps->bookSuspend) {
7916         // make sure opponent is not going to reply after receiving move to book position
7917         SendToProgram("force\n", cps);
7918         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7919     }
7920     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7921     // now arrange restart after book miss
7922     if(bookHit) {
7923         // after a book hit we never send 'go', and the code after the call to this routine
7924         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7925         char buf[MSG_SIZ], *move = bookHit;
7926         if(cps->useSAN) {
7927             int fromX, fromY, toX, toY;
7928             char promoChar;
7929             ChessMove moveType;
7930             move = buf + 30;
7931             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7932                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7933                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7934                                     PosFlags(forwardMostMove),
7935                                     fromY, fromX, toY, toX, promoChar, move);
7936             } else {
7937                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7938                 bookHit = NULL;
7939             }
7940         }
7941         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7942         SendToProgram(buf, cps);
7943         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7944     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7945         SendToProgram("go\n", cps);
7946         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7947     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7948         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7949             SendToProgram("go\n", cps);
7950         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7951     }
7952     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7953 }
7954
7955 int
7956 LoadError (char *errmess, ChessProgramState *cps)
7957 {   // unloads engine and switches back to -ncp mode if it was first
7958     if(cps->initDone) return FALSE;
7959     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7960     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7961     cps->pr = NoProc; 
7962     if(cps == &first) {
7963         appData.noChessProgram = TRUE;
7964         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7965         gameMode = BeginningOfGame; ModeHighlight();
7966         SetNCPMode();
7967     }
7968     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7969     DisplayMessage("", ""); // erase waiting message
7970     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7971     return TRUE;
7972 }
7973
7974 char *savedMessage;
7975 ChessProgramState *savedState;
7976 void
7977 DeferredBookMove (void)
7978 {
7979         if(savedState->lastPing != savedState->lastPong)
7980                     ScheduleDelayedEvent(DeferredBookMove, 10);
7981         else
7982         HandleMachineMove(savedMessage, savedState);
7983 }
7984
7985 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7986
7987 void
7988 HandleMachineMove (char *message, ChessProgramState *cps)
7989 {
7990     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7991     char realname[MSG_SIZ];
7992     int fromX, fromY, toX, toY;
7993     ChessMove moveType;
7994     char promoChar;
7995     char *p, *pv=buf1;
7996     int machineWhite, oldError;
7997     char *bookHit;
7998
7999     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8000         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8001         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8002             DisplayError(_("Invalid pairing from pairing engine"), 0);
8003             return;
8004         }
8005         pairingReceived = 1;
8006         NextMatchGame();
8007         return; // Skim the pairing messages here.
8008     }
8009
8010     oldError = cps->userError; cps->userError = 0;
8011
8012 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8013     /*
8014      * Kludge to ignore BEL characters
8015      */
8016     while (*message == '\007') message++;
8017
8018     /*
8019      * [HGM] engine debug message: ignore lines starting with '#' character
8020      */
8021     if(cps->debug && *message == '#') return;
8022
8023     /*
8024      * Look for book output
8025      */
8026     if (cps == &first && bookRequested) {
8027         if (message[0] == '\t' || message[0] == ' ') {
8028             /* Part of the book output is here; append it */
8029             strcat(bookOutput, message);
8030             strcat(bookOutput, "  \n");
8031             return;
8032         } else if (bookOutput[0] != NULLCHAR) {
8033             /* All of book output has arrived; display it */
8034             char *p = bookOutput;
8035             while (*p != NULLCHAR) {
8036                 if (*p == '\t') *p = ' ';
8037                 p++;
8038             }
8039             DisplayInformation(bookOutput);
8040             bookRequested = FALSE;
8041             /* Fall through to parse the current output */
8042         }
8043     }
8044
8045     /*
8046      * Look for machine move.
8047      */
8048     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8049         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8050     {
8051         /* This method is only useful on engines that support ping */
8052         if (cps->lastPing != cps->lastPong) {
8053           if (gameMode == BeginningOfGame) {
8054             /* Extra move from before last new; ignore */
8055             if (appData.debugMode) {
8056                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8057             }
8058           } else {
8059             if (appData.debugMode) {
8060                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8061                         cps->which, gameMode);
8062             }
8063
8064             SendToProgram("undo\n", cps);
8065           }
8066           return;
8067         }
8068
8069         switch (gameMode) {
8070           case BeginningOfGame:
8071             /* Extra move from before last reset; ignore */
8072             if (appData.debugMode) {
8073                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8074             }
8075             return;
8076
8077           case EndOfGame:
8078           case IcsIdle:
8079           default:
8080             /* Extra move after we tried to stop.  The mode test is
8081                not a reliable way of detecting this problem, but it's
8082                the best we can do on engines that don't support ping.
8083             */
8084             if (appData.debugMode) {
8085                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8086                         cps->which, gameMode);
8087             }
8088             SendToProgram("undo\n", cps);
8089             return;
8090
8091           case MachinePlaysWhite:
8092           case IcsPlayingWhite:
8093             machineWhite = TRUE;
8094             break;
8095
8096           case MachinePlaysBlack:
8097           case IcsPlayingBlack:
8098             machineWhite = FALSE;
8099             break;
8100
8101           case TwoMachinesPlay:
8102             machineWhite = (cps->twoMachinesColor[0] == 'w');
8103             break;
8104         }
8105         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8106             if (appData.debugMode) {
8107                 fprintf(debugFP,
8108                         "Ignoring move out of turn by %s, gameMode %d"
8109                         ", forwardMost %d\n",
8110                         cps->which, gameMode, forwardMostMove);
8111             }
8112             return;
8113         }
8114
8115         if(cps->alphaRank) AlphaRank(machineMove, 4);
8116         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8117                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8118             /* Machine move could not be parsed; ignore it. */
8119           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8120                     machineMove, _(cps->which));
8121             DisplayError(buf1, 0);
8122             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8123                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8124             if (gameMode == TwoMachinesPlay) {
8125               GameEnds(machineWhite ? BlackWins : WhiteWins,
8126                        buf1, GE_XBOARD);
8127             }
8128             return;
8129         }
8130
8131         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8132         /* So we have to redo legality test with true e.p. status here,  */
8133         /* to make sure an illegal e.p. capture does not slip through,   */
8134         /* to cause a forfeit on a justified illegal-move complaint      */
8135         /* of the opponent.                                              */
8136         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8137            ChessMove moveType;
8138            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8139                              fromY, fromX, toY, toX, promoChar);
8140             if(moveType == IllegalMove) {
8141               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8142                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8143                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8144                            buf1, GE_XBOARD);
8145                 return;
8146            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8147            /* [HGM] Kludge to handle engines that send FRC-style castling
8148               when they shouldn't (like TSCP-Gothic) */
8149            switch(moveType) {
8150              case WhiteASideCastleFR:
8151              case BlackASideCastleFR:
8152                toX+=2;
8153                currentMoveString[2]++;
8154                break;
8155              case WhiteHSideCastleFR:
8156              case BlackHSideCastleFR:
8157                toX--;
8158                currentMoveString[2]--;
8159                break;
8160              default: ; // nothing to do, but suppresses warning of pedantic compilers
8161            }
8162         }
8163         hintRequested = FALSE;
8164         lastHint[0] = NULLCHAR;
8165         bookRequested = FALSE;
8166         /* Program may be pondering now */
8167         cps->maybeThinking = TRUE;
8168         if (cps->sendTime == 2) cps->sendTime = 1;
8169         if (cps->offeredDraw) cps->offeredDraw--;
8170
8171         /* [AS] Save move info*/
8172         pvInfoList[ forwardMostMove ].score = programStats.score;
8173         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8174         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8175
8176         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8177
8178         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8179         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8180             int count = 0;
8181
8182             while( count < adjudicateLossPlies ) {
8183                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8184
8185                 if( count & 1 ) {
8186                     score = -score; /* Flip score for winning side */
8187                 }
8188
8189                 if( score > adjudicateLossThreshold ) {
8190                     break;
8191                 }
8192
8193                 count++;
8194             }
8195
8196             if( count >= adjudicateLossPlies ) {
8197                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8198
8199                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8200                     "Xboard adjudication",
8201                     GE_XBOARD );
8202
8203                 return;
8204             }
8205         }
8206
8207         if(Adjudicate(cps)) {
8208             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8209             return; // [HGM] adjudicate: for all automatic game ends
8210         }
8211
8212 #if ZIPPY
8213         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8214             first.initDone) {
8215           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8216                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8217                 SendToICS("draw ");
8218                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8219           }
8220           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8221           ics_user_moved = 1;
8222           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8223                 char buf[3*MSG_SIZ];
8224
8225                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8226                         programStats.score / 100.,
8227                         programStats.depth,
8228                         programStats.time / 100.,
8229                         (unsigned int)programStats.nodes,
8230                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8231                         programStats.movelist);
8232                 SendToICS(buf);
8233 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8234           }
8235         }
8236 #endif
8237
8238         /* [AS] Clear stats for next move */
8239         ClearProgramStats();
8240         thinkOutput[0] = NULLCHAR;
8241         hiddenThinkOutputState = 0;
8242
8243         bookHit = NULL;
8244         if (gameMode == TwoMachinesPlay) {
8245             /* [HGM] relaying draw offers moved to after reception of move */
8246             /* and interpreting offer as claim if it brings draw condition */
8247             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8248                 SendToProgram("draw\n", cps->other);
8249             }
8250             if (cps->other->sendTime) {
8251                 SendTimeRemaining(cps->other,
8252                                   cps->other->twoMachinesColor[0] == 'w');
8253             }
8254             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8255             if (firstMove && !bookHit) {
8256                 firstMove = FALSE;
8257                 if (cps->other->useColors) {
8258                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8259                 }
8260                 SendToProgram("go\n", cps->other);
8261             }
8262             cps->other->maybeThinking = TRUE;
8263         }
8264
8265         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8266
8267         if (!pausing && appData.ringBellAfterMoves) {
8268             RingBell();
8269         }
8270
8271         /*
8272          * Reenable menu items that were disabled while
8273          * machine was thinking
8274          */
8275         if (gameMode != TwoMachinesPlay)
8276             SetUserThinkingEnables();
8277
8278         // [HGM] book: after book hit opponent has received move and is now in force mode
8279         // force the book reply into it, and then fake that it outputted this move by jumping
8280         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8281         if(bookHit) {
8282                 static char bookMove[MSG_SIZ]; // a bit generous?
8283
8284                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8285                 strcat(bookMove, bookHit);
8286                 message = bookMove;
8287                 cps = cps->other;
8288                 programStats.nodes = programStats.depth = programStats.time =
8289                 programStats.score = programStats.got_only_move = 0;
8290                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8291
8292                 if(cps->lastPing != cps->lastPong) {
8293                     savedMessage = message; // args for deferred call
8294                     savedState = cps;
8295                     ScheduleDelayedEvent(DeferredBookMove, 10);
8296                     return;
8297                 }
8298                 goto FakeBookMove;
8299         }
8300
8301         return;
8302     }
8303
8304     /* Set special modes for chess engines.  Later something general
8305      *  could be added here; for now there is just one kludge feature,
8306      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8307      *  when "xboard" is given as an interactive command.
8308      */
8309     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8310         cps->useSigint = FALSE;
8311         cps->useSigterm = FALSE;
8312     }
8313     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8314       ParseFeatures(message+8, cps);
8315       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8316     }
8317
8318     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8319                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8320       int dummy, s=6; char buf[MSG_SIZ];
8321       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8322       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8323       if(startedFromSetupPosition) return;
8324       ParseFEN(boards[0], &dummy, message+s);
8325       DrawPosition(TRUE, boards[0]);
8326       startedFromSetupPosition = TRUE;
8327       return;
8328     }
8329     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8330      * want this, I was asked to put it in, and obliged.
8331      */
8332     if (!strncmp(message, "setboard ", 9)) {
8333         Board initial_position;
8334
8335         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8336
8337         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8338             DisplayError(_("Bad FEN received from engine"), 0);
8339             return ;
8340         } else {
8341            Reset(TRUE, FALSE);
8342            CopyBoard(boards[0], initial_position);
8343            initialRulePlies = FENrulePlies;
8344            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8345            else gameMode = MachinePlaysBlack;
8346            DrawPosition(FALSE, boards[currentMove]);
8347         }
8348         return;
8349     }
8350
8351     /*
8352      * Look for communication commands
8353      */
8354     if (!strncmp(message, "telluser ", 9)) {
8355         if(message[9] == '\\' && message[10] == '\\')
8356             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8357         PlayTellSound();
8358         DisplayNote(message + 9);
8359         return;
8360     }
8361     if (!strncmp(message, "tellusererror ", 14)) {
8362         cps->userError = 1;
8363         if(message[14] == '\\' && message[15] == '\\')
8364             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8365         PlayTellSound();
8366         DisplayError(message + 14, 0);
8367         return;
8368     }
8369     if (!strncmp(message, "tellopponent ", 13)) {
8370       if (appData.icsActive) {
8371         if (loggedOn) {
8372           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8373           SendToICS(buf1);
8374         }
8375       } else {
8376         DisplayNote(message + 13);
8377       }
8378       return;
8379     }
8380     if (!strncmp(message, "tellothers ", 11)) {
8381       if (appData.icsActive) {
8382         if (loggedOn) {
8383           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8384           SendToICS(buf1);
8385         }
8386       }
8387       return;
8388     }
8389     if (!strncmp(message, "tellall ", 8)) {
8390       if (appData.icsActive) {
8391         if (loggedOn) {
8392           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8393           SendToICS(buf1);
8394         }
8395       } else {
8396         DisplayNote(message + 8);
8397       }
8398       return;
8399     }
8400     if (strncmp(message, "warning", 7) == 0) {
8401         /* Undocumented feature, use tellusererror in new code */
8402         DisplayError(message, 0);
8403         return;
8404     }
8405     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8406         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8407         strcat(realname, " query");
8408         AskQuestion(realname, buf2, buf1, cps->pr);
8409         return;
8410     }
8411     /* Commands from the engine directly to ICS.  We don't allow these to be
8412      *  sent until we are logged on. Crafty kibitzes have been known to
8413      *  interfere with the login process.
8414      */
8415     if (loggedOn) {
8416         if (!strncmp(message, "tellics ", 8)) {
8417             SendToICS(message + 8);
8418             SendToICS("\n");
8419             return;
8420         }
8421         if (!strncmp(message, "tellicsnoalias ", 15)) {
8422             SendToICS(ics_prefix);
8423             SendToICS(message + 15);
8424             SendToICS("\n");
8425             return;
8426         }
8427         /* The following are for backward compatibility only */
8428         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8429             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8430             SendToICS(ics_prefix);
8431             SendToICS(message);
8432             SendToICS("\n");
8433             return;
8434         }
8435     }
8436     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8437         return;
8438     }
8439     /*
8440      * If the move is illegal, cancel it and redraw the board.
8441      * Also deal with other error cases.  Matching is rather loose
8442      * here to accommodate engines written before the spec.
8443      */
8444     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8445         strncmp(message, "Error", 5) == 0) {
8446         if (StrStr(message, "name") ||
8447             StrStr(message, "rating") || StrStr(message, "?") ||
8448             StrStr(message, "result") || StrStr(message, "board") ||
8449             StrStr(message, "bk") || StrStr(message, "computer") ||
8450             StrStr(message, "variant") || StrStr(message, "hint") ||
8451             StrStr(message, "random") || StrStr(message, "depth") ||
8452             StrStr(message, "accepted")) {
8453             return;
8454         }
8455         if (StrStr(message, "protover")) {
8456           /* Program is responding to input, so it's apparently done
8457              initializing, and this error message indicates it is
8458              protocol version 1.  So we don't need to wait any longer
8459              for it to initialize and send feature commands. */
8460           FeatureDone(cps, 1);
8461           cps->protocolVersion = 1;
8462           return;
8463         }
8464         cps->maybeThinking = FALSE;
8465
8466         if (StrStr(message, "draw")) {
8467             /* Program doesn't have "draw" command */
8468             cps->sendDrawOffers = 0;
8469             return;
8470         }
8471         if (cps->sendTime != 1 &&
8472             (StrStr(message, "time") || StrStr(message, "otim"))) {
8473           /* Program apparently doesn't have "time" or "otim" command */
8474           cps->sendTime = 0;
8475           return;
8476         }
8477         if (StrStr(message, "analyze")) {
8478             cps->analysisSupport = FALSE;
8479             cps->analyzing = FALSE;
8480 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8481             EditGameEvent(); // [HGM] try to preserve loaded game
8482             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8483             DisplayError(buf2, 0);
8484             return;
8485         }
8486         if (StrStr(message, "(no matching move)st")) {
8487           /* Special kludge for GNU Chess 4 only */
8488           cps->stKludge = TRUE;
8489           SendTimeControl(cps, movesPerSession, timeControl,
8490                           timeIncrement, appData.searchDepth,
8491                           searchTime);
8492           return;
8493         }
8494         if (StrStr(message, "(no matching move)sd")) {
8495           /* Special kludge for GNU Chess 4 only */
8496           cps->sdKludge = TRUE;
8497           SendTimeControl(cps, movesPerSession, timeControl,
8498                           timeIncrement, appData.searchDepth,
8499                           searchTime);
8500           return;
8501         }
8502         if (!StrStr(message, "llegal")) {
8503             return;
8504         }
8505         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8506             gameMode == IcsIdle) return;
8507         if (forwardMostMove <= backwardMostMove) return;
8508         if (pausing) PauseEvent();
8509       if(appData.forceIllegal) {
8510             // [HGM] illegal: machine refused move; force position after move into it
8511           SendToProgram("force\n", cps);
8512           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8513                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8514                 // when black is to move, while there might be nothing on a2 or black
8515                 // might already have the move. So send the board as if white has the move.
8516                 // But first we must change the stm of the engine, as it refused the last move
8517                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8518                 if(WhiteOnMove(forwardMostMove)) {
8519                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8520                     SendBoard(cps, forwardMostMove); // kludgeless board
8521                 } else {
8522                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8523                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8524                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8525                 }
8526           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8527             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8528                  gameMode == TwoMachinesPlay)
8529               SendToProgram("go\n", cps);
8530             return;
8531       } else
8532         if (gameMode == PlayFromGameFile) {
8533             /* Stop reading this game file */
8534             gameMode = EditGame;
8535             ModeHighlight();
8536         }
8537         /* [HGM] illegal-move claim should forfeit game when Xboard */
8538         /* only passes fully legal moves                            */
8539         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8540             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8541                                 "False illegal-move claim", GE_XBOARD );
8542             return; // do not take back move we tested as valid
8543         }
8544         currentMove = forwardMostMove-1;
8545         DisplayMove(currentMove-1); /* before DisplayMoveError */
8546         SwitchClocks(forwardMostMove-1); // [HGM] race
8547         DisplayBothClocks();
8548         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8549                 parseList[currentMove], _(cps->which));
8550         DisplayMoveError(buf1);
8551         DrawPosition(FALSE, boards[currentMove]);
8552
8553         SetUserThinkingEnables();
8554         return;
8555     }
8556     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8557         /* Program has a broken "time" command that
8558            outputs a string not ending in newline.
8559            Don't use it. */
8560         cps->sendTime = 0;
8561     }
8562
8563     /*
8564      * If chess program startup fails, exit with an error message.
8565      * Attempts to recover here are futile. [HGM] Well, we try anyway
8566      */
8567     if ((StrStr(message, "unknown host") != NULL)
8568         || (StrStr(message, "No remote directory") != NULL)
8569         || (StrStr(message, "not found") != NULL)
8570         || (StrStr(message, "No such file") != NULL)
8571         || (StrStr(message, "can't alloc") != NULL)
8572         || (StrStr(message, "Permission denied") != NULL)) {
8573
8574         cps->maybeThinking = FALSE;
8575         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8576                 _(cps->which), cps->program, cps->host, message);
8577         RemoveInputSource(cps->isr);
8578         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8579             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8580             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8581         }
8582         return;
8583     }
8584
8585     /*
8586      * Look for hint output
8587      */
8588     if (sscanf(message, "Hint: %s", buf1) == 1) {
8589         if (cps == &first && hintRequested) {
8590             hintRequested = FALSE;
8591             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8592                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8593                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8594                                     PosFlags(forwardMostMove),
8595                                     fromY, fromX, toY, toX, promoChar, buf1);
8596                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8597                 DisplayInformation(buf2);
8598             } else {
8599                 /* Hint move could not be parsed!? */
8600               snprintf(buf2, sizeof(buf2),
8601                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8602                         buf1, _(cps->which));
8603                 DisplayError(buf2, 0);
8604             }
8605         } else {
8606           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8607         }
8608         return;
8609     }
8610
8611     /*
8612      * Ignore other messages if game is not in progress
8613      */
8614     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8615         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8616
8617     /*
8618      * look for win, lose, draw, or draw offer
8619      */
8620     if (strncmp(message, "1-0", 3) == 0) {
8621         char *p, *q, *r = "";
8622         p = strchr(message, '{');
8623         if (p) {
8624             q = strchr(p, '}');
8625             if (q) {
8626                 *q = NULLCHAR;
8627                 r = p + 1;
8628             }
8629         }
8630         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8631         return;
8632     } else if (strncmp(message, "0-1", 3) == 0) {
8633         char *p, *q, *r = "";
8634         p = strchr(message, '{');
8635         if (p) {
8636             q = strchr(p, '}');
8637             if (q) {
8638                 *q = NULLCHAR;
8639                 r = p + 1;
8640             }
8641         }
8642         /* Kludge for Arasan 4.1 bug */
8643         if (strcmp(r, "Black resigns") == 0) {
8644             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8645             return;
8646         }
8647         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8648         return;
8649     } else if (strncmp(message, "1/2", 3) == 0) {
8650         char *p, *q, *r = "";
8651         p = strchr(message, '{');
8652         if (p) {
8653             q = strchr(p, '}');
8654             if (q) {
8655                 *q = NULLCHAR;
8656                 r = p + 1;
8657             }
8658         }
8659
8660         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8661         return;
8662
8663     } else if (strncmp(message, "White resign", 12) == 0) {
8664         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8665         return;
8666     } else if (strncmp(message, "Black resign", 12) == 0) {
8667         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8668         return;
8669     } else if (strncmp(message, "White matches", 13) == 0 ||
8670                strncmp(message, "Black matches", 13) == 0   ) {
8671         /* [HGM] ignore GNUShogi noises */
8672         return;
8673     } else if (strncmp(message, "White", 5) == 0 &&
8674                message[5] != '(' &&
8675                StrStr(message, "Black") == NULL) {
8676         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8677         return;
8678     } else if (strncmp(message, "Black", 5) == 0 &&
8679                message[5] != '(') {
8680         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8681         return;
8682     } else if (strcmp(message, "resign") == 0 ||
8683                strcmp(message, "computer resigns") == 0) {
8684         switch (gameMode) {
8685           case MachinePlaysBlack:
8686           case IcsPlayingBlack:
8687             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8688             break;
8689           case MachinePlaysWhite:
8690           case IcsPlayingWhite:
8691             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8692             break;
8693           case TwoMachinesPlay:
8694             if (cps->twoMachinesColor[0] == 'w')
8695               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8696             else
8697               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8698             break;
8699           default:
8700             /* can't happen */
8701             break;
8702         }
8703         return;
8704     } else if (strncmp(message, "opponent mates", 14) == 0) {
8705         switch (gameMode) {
8706           case MachinePlaysBlack:
8707           case IcsPlayingBlack:
8708             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8709             break;
8710           case MachinePlaysWhite:
8711           case IcsPlayingWhite:
8712             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8713             break;
8714           case TwoMachinesPlay:
8715             if (cps->twoMachinesColor[0] == 'w')
8716               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8717             else
8718               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8719             break;
8720           default:
8721             /* can't happen */
8722             break;
8723         }
8724         return;
8725     } else if (strncmp(message, "computer mates", 14) == 0) {
8726         switch (gameMode) {
8727           case MachinePlaysBlack:
8728           case IcsPlayingBlack:
8729             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8730             break;
8731           case MachinePlaysWhite:
8732           case IcsPlayingWhite:
8733             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8734             break;
8735           case TwoMachinesPlay:
8736             if (cps->twoMachinesColor[0] == 'w')
8737               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8738             else
8739               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8740             break;
8741           default:
8742             /* can't happen */
8743             break;
8744         }
8745         return;
8746     } else if (strncmp(message, "checkmate", 9) == 0) {
8747         if (WhiteOnMove(forwardMostMove)) {
8748             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8749         } else {
8750             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8751         }
8752         return;
8753     } else if (strstr(message, "Draw") != NULL ||
8754                strstr(message, "game is a draw") != NULL) {
8755         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8756         return;
8757     } else if (strstr(message, "offer") != NULL &&
8758                strstr(message, "draw") != NULL) {
8759 #if ZIPPY
8760         if (appData.zippyPlay && first.initDone) {
8761             /* Relay offer to ICS */
8762             SendToICS(ics_prefix);
8763             SendToICS("draw\n");
8764         }
8765 #endif
8766         cps->offeredDraw = 2; /* valid until this engine moves twice */
8767         if (gameMode == TwoMachinesPlay) {
8768             if (cps->other->offeredDraw) {
8769                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8770             /* [HGM] in two-machine mode we delay relaying draw offer      */
8771             /* until after we also have move, to see if it is really claim */
8772             }
8773         } else if (gameMode == MachinePlaysWhite ||
8774                    gameMode == MachinePlaysBlack) {
8775           if (userOfferedDraw) {
8776             DisplayInformation(_("Machine accepts your draw offer"));
8777             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8778           } else {
8779             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8780           }
8781         }
8782     }
8783
8784
8785     /*
8786      * Look for thinking output
8787      */
8788     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8789           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8790                                 ) {
8791         int plylev, mvleft, mvtot, curscore, time;
8792         char mvname[MOVE_LEN];
8793         u64 nodes; // [DM]
8794         char plyext;
8795         int ignore = FALSE;
8796         int prefixHint = FALSE;
8797         mvname[0] = NULLCHAR;
8798
8799         switch (gameMode) {
8800           case MachinePlaysBlack:
8801           case IcsPlayingBlack:
8802             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8803             break;
8804           case MachinePlaysWhite:
8805           case IcsPlayingWhite:
8806             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8807             break;
8808           case AnalyzeMode:
8809           case AnalyzeFile:
8810             break;
8811           case IcsObserving: /* [DM] icsEngineAnalyze */
8812             if (!appData.icsEngineAnalyze) ignore = TRUE;
8813             break;
8814           case TwoMachinesPlay:
8815             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8816                 ignore = TRUE;
8817             }
8818             break;
8819           default:
8820             ignore = TRUE;
8821             break;
8822         }
8823
8824         if (!ignore) {
8825             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8826             buf1[0] = NULLCHAR;
8827             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8828                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8829
8830                 if (plyext != ' ' && plyext != '\t') {
8831                     time *= 100;
8832                 }
8833
8834                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8835                 if( cps->scoreIsAbsolute &&
8836                     ( gameMode == MachinePlaysBlack ||
8837                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8838                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8839                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8840                      !WhiteOnMove(currentMove)
8841                     ) )
8842                 {
8843                     curscore = -curscore;
8844                 }
8845
8846                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8847
8848                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8849                         char buf[MSG_SIZ];
8850                         FILE *f;
8851                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8852                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8853                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8854                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8855                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8856                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8857                                 fclose(f);
8858                         } else DisplayError(_("failed writing PV"), 0);
8859                 }
8860
8861                 tempStats.depth = plylev;
8862                 tempStats.nodes = nodes;
8863                 tempStats.time = time;
8864                 tempStats.score = curscore;
8865                 tempStats.got_only_move = 0;
8866
8867                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8868                         int ticklen;
8869
8870                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8871                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8872                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8873                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8874                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8875                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8876                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8877                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8878                 }
8879
8880                 /* Buffer overflow protection */
8881                 if (pv[0] != NULLCHAR) {
8882                     if (strlen(pv) >= sizeof(tempStats.movelist)
8883                         && appData.debugMode) {
8884                         fprintf(debugFP,
8885                                 "PV is too long; using the first %u bytes.\n",
8886                                 (unsigned) sizeof(tempStats.movelist) - 1);
8887                     }
8888
8889                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8890                 } else {
8891                     sprintf(tempStats.movelist, " no PV\n");
8892                 }
8893
8894                 if (tempStats.seen_stat) {
8895                     tempStats.ok_to_send = 1;
8896                 }
8897
8898                 if (strchr(tempStats.movelist, '(') != NULL) {
8899                     tempStats.line_is_book = 1;
8900                     tempStats.nr_moves = 0;
8901                     tempStats.moves_left = 0;
8902                 } else {
8903                     tempStats.line_is_book = 0;
8904                 }
8905
8906                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8907                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8908
8909                 SendProgramStatsToFrontend( cps, &tempStats );
8910
8911                 /*
8912                     [AS] Protect the thinkOutput buffer from overflow... this
8913                     is only useful if buf1 hasn't overflowed first!
8914                 */
8915                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8916                          plylev,
8917                          (gameMode == TwoMachinesPlay ?
8918                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8919                          ((double) curscore) / 100.0,
8920                          prefixHint ? lastHint : "",
8921                          prefixHint ? " " : "" );
8922
8923                 if( buf1[0] != NULLCHAR ) {
8924                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8925
8926                     if( strlen(pv) > max_len ) {
8927                         if( appData.debugMode) {
8928                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8929                         }
8930                         pv[max_len+1] = '\0';
8931                     }
8932
8933                     strcat( thinkOutput, pv);
8934                 }
8935
8936                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8937                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8938                     DisplayMove(currentMove - 1);
8939                 }
8940                 return;
8941
8942             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8943                 /* crafty (9.25+) says "(only move) <move>"
8944                  * if there is only 1 legal move
8945                  */
8946                 sscanf(p, "(only move) %s", buf1);
8947                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8948                 sprintf(programStats.movelist, "%s (only move)", buf1);
8949                 programStats.depth = 1;
8950                 programStats.nr_moves = 1;
8951                 programStats.moves_left = 1;
8952                 programStats.nodes = 1;
8953                 programStats.time = 1;
8954                 programStats.got_only_move = 1;
8955
8956                 /* Not really, but we also use this member to
8957                    mean "line isn't going to change" (Crafty
8958                    isn't searching, so stats won't change) */
8959                 programStats.line_is_book = 1;
8960
8961                 SendProgramStatsToFrontend( cps, &programStats );
8962
8963                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8964                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8965                     DisplayMove(currentMove - 1);
8966                 }
8967                 return;
8968             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8969                               &time, &nodes, &plylev, &mvleft,
8970                               &mvtot, mvname) >= 5) {
8971                 /* The stat01: line is from Crafty (9.29+) in response
8972                    to the "." command */
8973                 programStats.seen_stat = 1;
8974                 cps->maybeThinking = TRUE;
8975
8976                 if (programStats.got_only_move || !appData.periodicUpdates)
8977                   return;
8978
8979                 programStats.depth = plylev;
8980                 programStats.time = time;
8981                 programStats.nodes = nodes;
8982                 programStats.moves_left = mvleft;
8983                 programStats.nr_moves = mvtot;
8984                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8985                 programStats.ok_to_send = 1;
8986                 programStats.movelist[0] = '\0';
8987
8988                 SendProgramStatsToFrontend( cps, &programStats );
8989
8990                 return;
8991
8992             } else if (strncmp(message,"++",2) == 0) {
8993                 /* Crafty 9.29+ outputs this */
8994                 programStats.got_fail = 2;
8995                 return;
8996
8997             } else if (strncmp(message,"--",2) == 0) {
8998                 /* Crafty 9.29+ outputs this */
8999                 programStats.got_fail = 1;
9000                 return;
9001
9002             } else if (thinkOutput[0] != NULLCHAR &&
9003                        strncmp(message, "    ", 4) == 0) {
9004                 unsigned message_len;
9005
9006                 p = message;
9007                 while (*p && *p == ' ') p++;
9008
9009                 message_len = strlen( p );
9010
9011                 /* [AS] Avoid buffer overflow */
9012                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9013                     strcat(thinkOutput, " ");
9014                     strcat(thinkOutput, p);
9015                 }
9016
9017                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9018                     strcat(programStats.movelist, " ");
9019                     strcat(programStats.movelist, p);
9020                 }
9021
9022                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9023                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9024                     DisplayMove(currentMove - 1);
9025                 }
9026                 return;
9027             }
9028         }
9029         else {
9030             buf1[0] = NULLCHAR;
9031
9032             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9033                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9034             {
9035                 ChessProgramStats cpstats;
9036
9037                 if (plyext != ' ' && plyext != '\t') {
9038                     time *= 100;
9039                 }
9040
9041                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9042                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9043                     curscore = -curscore;
9044                 }
9045
9046                 cpstats.depth = plylev;
9047                 cpstats.nodes = nodes;
9048                 cpstats.time = time;
9049                 cpstats.score = curscore;
9050                 cpstats.got_only_move = 0;
9051                 cpstats.movelist[0] = '\0';
9052
9053                 if (buf1[0] != NULLCHAR) {
9054                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9055                 }
9056
9057                 cpstats.ok_to_send = 0;
9058                 cpstats.line_is_book = 0;
9059                 cpstats.nr_moves = 0;
9060                 cpstats.moves_left = 0;
9061
9062                 SendProgramStatsToFrontend( cps, &cpstats );
9063             }
9064         }
9065     }
9066 }
9067
9068
9069 /* Parse a game score from the character string "game", and
9070    record it as the history of the current game.  The game
9071    score is NOT assumed to start from the standard position.
9072    The display is not updated in any way.
9073    */
9074 void
9075 ParseGameHistory (char *game)
9076 {
9077     ChessMove moveType;
9078     int fromX, fromY, toX, toY, boardIndex;
9079     char promoChar;
9080     char *p, *q;
9081     char buf[MSG_SIZ];
9082
9083     if (appData.debugMode)
9084       fprintf(debugFP, "Parsing game history: %s\n", game);
9085
9086     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9087     gameInfo.site = StrSave(appData.icsHost);
9088     gameInfo.date = PGNDate();
9089     gameInfo.round = StrSave("-");
9090
9091     /* Parse out names of players */
9092     while (*game == ' ') game++;
9093     p = buf;
9094     while (*game != ' ') *p++ = *game++;
9095     *p = NULLCHAR;
9096     gameInfo.white = StrSave(buf);
9097     while (*game == ' ') game++;
9098     p = buf;
9099     while (*game != ' ' && *game != '\n') *p++ = *game++;
9100     *p = NULLCHAR;
9101     gameInfo.black = StrSave(buf);
9102
9103     /* Parse moves */
9104     boardIndex = blackPlaysFirst ? 1 : 0;
9105     yynewstr(game);
9106     for (;;) {
9107         yyboardindex = boardIndex;
9108         moveType = (ChessMove) Myylex();
9109         switch (moveType) {
9110           case IllegalMove:             /* maybe suicide chess, etc. */
9111   if (appData.debugMode) {
9112     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9113     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9114     setbuf(debugFP, NULL);
9115   }
9116           case WhitePromotion:
9117           case BlackPromotion:
9118           case WhiteNonPromotion:
9119           case BlackNonPromotion:
9120           case NormalMove:
9121           case WhiteCapturesEnPassant:
9122           case BlackCapturesEnPassant:
9123           case WhiteKingSideCastle:
9124           case WhiteQueenSideCastle:
9125           case BlackKingSideCastle:
9126           case BlackQueenSideCastle:
9127           case WhiteKingSideCastleWild:
9128           case WhiteQueenSideCastleWild:
9129           case BlackKingSideCastleWild:
9130           case BlackQueenSideCastleWild:
9131           /* PUSH Fabien */
9132           case WhiteHSideCastleFR:
9133           case WhiteASideCastleFR:
9134           case BlackHSideCastleFR:
9135           case BlackASideCastleFR:
9136           /* POP Fabien */
9137             fromX = currentMoveString[0] - AAA;
9138             fromY = currentMoveString[1] - ONE;
9139             toX = currentMoveString[2] - AAA;
9140             toY = currentMoveString[3] - ONE;
9141             promoChar = currentMoveString[4];
9142             break;
9143           case WhiteDrop:
9144           case BlackDrop:
9145             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9146             fromX = moveType == WhiteDrop ?
9147               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9148             (int) CharToPiece(ToLower(currentMoveString[0]));
9149             fromY = DROP_RANK;
9150             toX = currentMoveString[2] - AAA;
9151             toY = currentMoveString[3] - ONE;
9152             promoChar = NULLCHAR;
9153             break;
9154           case AmbiguousMove:
9155             /* bug? */
9156             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9157   if (appData.debugMode) {
9158     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9159     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9160     setbuf(debugFP, NULL);
9161   }
9162             DisplayError(buf, 0);
9163             return;
9164           case ImpossibleMove:
9165             /* bug? */
9166             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9167   if (appData.debugMode) {
9168     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9169     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9170     setbuf(debugFP, NULL);
9171   }
9172             DisplayError(buf, 0);
9173             return;
9174           case EndOfFile:
9175             if (boardIndex < backwardMostMove) {
9176                 /* Oops, gap.  How did that happen? */
9177                 DisplayError(_("Gap in move list"), 0);
9178                 return;
9179             }
9180             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9181             if (boardIndex > forwardMostMove) {
9182                 forwardMostMove = boardIndex;
9183             }
9184             return;
9185           case ElapsedTime:
9186             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9187                 strcat(parseList[boardIndex-1], " ");
9188                 strcat(parseList[boardIndex-1], yy_text);
9189             }
9190             continue;
9191           case Comment:
9192           case PGNTag:
9193           case NAG:
9194           default:
9195             /* ignore */
9196             continue;
9197           case WhiteWins:
9198           case BlackWins:
9199           case GameIsDrawn:
9200           case GameUnfinished:
9201             if (gameMode == IcsExamining) {
9202                 if (boardIndex < backwardMostMove) {
9203                     /* Oops, gap.  How did that happen? */
9204                     return;
9205                 }
9206                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9207                 return;
9208             }
9209             gameInfo.result = moveType;
9210             p = strchr(yy_text, '{');
9211             if (p == NULL) p = strchr(yy_text, '(');
9212             if (p == NULL) {
9213                 p = yy_text;
9214                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9215             } else {
9216                 q = strchr(p, *p == '{' ? '}' : ')');
9217                 if (q != NULL) *q = NULLCHAR;
9218                 p++;
9219             }
9220             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9221             gameInfo.resultDetails = StrSave(p);
9222             continue;
9223         }
9224         if (boardIndex >= forwardMostMove &&
9225             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9226             backwardMostMove = blackPlaysFirst ? 1 : 0;
9227             return;
9228         }
9229         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9230                                  fromY, fromX, toY, toX, promoChar,
9231                                  parseList[boardIndex]);
9232         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9233         /* currentMoveString is set as a side-effect of yylex */
9234         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9235         strcat(moveList[boardIndex], "\n");
9236         boardIndex++;
9237         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9238         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9239           case MT_NONE:
9240           case MT_STALEMATE:
9241           default:
9242             break;
9243           case MT_CHECK:
9244             if(gameInfo.variant != VariantShogi)
9245                 strcat(parseList[boardIndex - 1], "+");
9246             break;
9247           case MT_CHECKMATE:
9248           case MT_STAINMATE:
9249             strcat(parseList[boardIndex - 1], "#");
9250             break;
9251         }
9252     }
9253 }
9254
9255
9256 /* Apply a move to the given board  */
9257 void
9258 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9259 {
9260   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9261   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9262
9263     /* [HGM] compute & store e.p. status and castling rights for new position */
9264     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9265
9266       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9267       oldEP = (signed char)board[EP_STATUS];
9268       board[EP_STATUS] = EP_NONE;
9269
9270   if (fromY == DROP_RANK) {
9271         /* must be first */
9272         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9273             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9274             return;
9275         }
9276         piece = board[toY][toX] = (ChessSquare) fromX;
9277   } else {
9278       int i;
9279
9280       if( board[toY][toX] != EmptySquare )
9281            board[EP_STATUS] = EP_CAPTURE;
9282
9283       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9284            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9285                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9286       } else
9287       if( board[fromY][fromX] == WhitePawn ) {
9288            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9289                board[EP_STATUS] = EP_PAWN_MOVE;
9290            if( toY-fromY==2) {
9291                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9292                         gameInfo.variant != VariantBerolina || toX < fromX)
9293                       board[EP_STATUS] = toX | berolina;
9294                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9295                         gameInfo.variant != VariantBerolina || toX > fromX)
9296                       board[EP_STATUS] = toX;
9297            }
9298       } else
9299       if( board[fromY][fromX] == BlackPawn ) {
9300            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9301                board[EP_STATUS] = EP_PAWN_MOVE;
9302            if( toY-fromY== -2) {
9303                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9304                         gameInfo.variant != VariantBerolina || toX < fromX)
9305                       board[EP_STATUS] = toX | berolina;
9306                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9307                         gameInfo.variant != VariantBerolina || toX > fromX)
9308                       board[EP_STATUS] = toX;
9309            }
9310        }
9311
9312        for(i=0; i<nrCastlingRights; i++) {
9313            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9314               board[CASTLING][i] == toX   && castlingRank[i] == toY
9315              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9316        }
9317
9318        if(gameInfo.variant == VariantSChess) { // update virginity
9319            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9320            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9321            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9322            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9323        }
9324
9325      if (fromX == toX && fromY == toY) return;
9326
9327      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9328      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9329      if(gameInfo.variant == VariantKnightmate)
9330          king += (int) WhiteUnicorn - (int) WhiteKing;
9331
9332     /* Code added by Tord: */
9333     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9334     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9335         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9336       board[fromY][fromX] = EmptySquare;
9337       board[toY][toX] = EmptySquare;
9338       if((toX > fromX) != (piece == WhiteRook)) {
9339         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9340       } else {
9341         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9342       }
9343     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9344                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9345       board[fromY][fromX] = EmptySquare;
9346       board[toY][toX] = EmptySquare;
9347       if((toX > fromX) != (piece == BlackRook)) {
9348         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9349       } else {
9350         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9351       }
9352     /* End of code added by Tord */
9353
9354     } else if (board[fromY][fromX] == king
9355         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9356         && toY == fromY && toX > fromX+1) {
9357         board[fromY][fromX] = EmptySquare;
9358         board[toY][toX] = king;
9359         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9360         board[fromY][BOARD_RGHT-1] = EmptySquare;
9361     } else if (board[fromY][fromX] == king
9362         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9363                && toY == fromY && toX < fromX-1) {
9364         board[fromY][fromX] = EmptySquare;
9365         board[toY][toX] = king;
9366         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9367         board[fromY][BOARD_LEFT] = EmptySquare;
9368     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9369                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9370                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9371                ) {
9372         /* white pawn promotion */
9373         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9374         if(gameInfo.variant==VariantBughouse ||
9375            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9376             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9377         board[fromY][fromX] = EmptySquare;
9378     } else if ((fromY >= BOARD_HEIGHT>>1)
9379                && (toX != fromX)
9380                && gameInfo.variant != VariantXiangqi
9381                && gameInfo.variant != VariantBerolina
9382                && (board[fromY][fromX] == WhitePawn)
9383                && (board[toY][toX] == EmptySquare)) {
9384         board[fromY][fromX] = EmptySquare;
9385         board[toY][toX] = WhitePawn;
9386         captured = board[toY - 1][toX];
9387         board[toY - 1][toX] = EmptySquare;
9388     } else if ((fromY == BOARD_HEIGHT-4)
9389                && (toX == fromX)
9390                && gameInfo.variant == VariantBerolina
9391                && (board[fromY][fromX] == WhitePawn)
9392                && (board[toY][toX] == EmptySquare)) {
9393         board[fromY][fromX] = EmptySquare;
9394         board[toY][toX] = WhitePawn;
9395         if(oldEP & EP_BEROLIN_A) {
9396                 captured = board[fromY][fromX-1];
9397                 board[fromY][fromX-1] = EmptySquare;
9398         }else{  captured = board[fromY][fromX+1];
9399                 board[fromY][fromX+1] = EmptySquare;
9400         }
9401     } else if (board[fromY][fromX] == king
9402         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9403                && toY == fromY && toX > fromX+1) {
9404         board[fromY][fromX] = EmptySquare;
9405         board[toY][toX] = king;
9406         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9407         board[fromY][BOARD_RGHT-1] = EmptySquare;
9408     } else if (board[fromY][fromX] == king
9409         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9410                && toY == fromY && toX < fromX-1) {
9411         board[fromY][fromX] = EmptySquare;
9412         board[toY][toX] = king;
9413         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9414         board[fromY][BOARD_LEFT] = EmptySquare;
9415     } else if (fromY == 7 && fromX == 3
9416                && board[fromY][fromX] == BlackKing
9417                && toY == 7 && toX == 5) {
9418         board[fromY][fromX] = EmptySquare;
9419         board[toY][toX] = BlackKing;
9420         board[fromY][7] = EmptySquare;
9421         board[toY][4] = BlackRook;
9422     } else if (fromY == 7 && fromX == 3
9423                && board[fromY][fromX] == BlackKing
9424                && toY == 7 && toX == 1) {
9425         board[fromY][fromX] = EmptySquare;
9426         board[toY][toX] = BlackKing;
9427         board[fromY][0] = EmptySquare;
9428         board[toY][2] = BlackRook;
9429     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9430                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9431                && toY < promoRank && promoChar
9432                ) {
9433         /* black pawn promotion */
9434         board[toY][toX] = CharToPiece(ToLower(promoChar));
9435         if(gameInfo.variant==VariantBughouse ||
9436            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9437             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9438         board[fromY][fromX] = EmptySquare;
9439     } else if ((fromY < BOARD_HEIGHT>>1)
9440                && (toX != fromX)
9441                && gameInfo.variant != VariantXiangqi
9442                && gameInfo.variant != VariantBerolina
9443                && (board[fromY][fromX] == BlackPawn)
9444                && (board[toY][toX] == EmptySquare)) {
9445         board[fromY][fromX] = EmptySquare;
9446         board[toY][toX] = BlackPawn;
9447         captured = board[toY + 1][toX];
9448         board[toY + 1][toX] = EmptySquare;
9449     } else if ((fromY == 3)
9450                && (toX == fromX)
9451                && gameInfo.variant == VariantBerolina
9452                && (board[fromY][fromX] == BlackPawn)
9453                && (board[toY][toX] == EmptySquare)) {
9454         board[fromY][fromX] = EmptySquare;
9455         board[toY][toX] = BlackPawn;
9456         if(oldEP & EP_BEROLIN_A) {
9457                 captured = board[fromY][fromX-1];
9458                 board[fromY][fromX-1] = EmptySquare;
9459         }else{  captured = board[fromY][fromX+1];
9460                 board[fromY][fromX+1] = EmptySquare;
9461         }
9462     } else {
9463         board[toY][toX] = board[fromY][fromX];
9464         board[fromY][fromX] = EmptySquare;
9465     }
9466   }
9467
9468     if (gameInfo.holdingsWidth != 0) {
9469
9470       /* !!A lot more code needs to be written to support holdings  */
9471       /* [HGM] OK, so I have written it. Holdings are stored in the */
9472       /* penultimate board files, so they are automaticlly stored   */
9473       /* in the game history.                                       */
9474       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9475                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9476         /* Delete from holdings, by decreasing count */
9477         /* and erasing image if necessary            */
9478         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9479         if(p < (int) BlackPawn) { /* white drop */
9480              p -= (int)WhitePawn;
9481                  p = PieceToNumber((ChessSquare)p);
9482              if(p >= gameInfo.holdingsSize) p = 0;
9483              if(--board[p][BOARD_WIDTH-2] <= 0)
9484                   board[p][BOARD_WIDTH-1] = EmptySquare;
9485              if((int)board[p][BOARD_WIDTH-2] < 0)
9486                         board[p][BOARD_WIDTH-2] = 0;
9487         } else {                  /* black drop */
9488              p -= (int)BlackPawn;
9489                  p = PieceToNumber((ChessSquare)p);
9490              if(p >= gameInfo.holdingsSize) p = 0;
9491              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9492                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9493              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9494                         board[BOARD_HEIGHT-1-p][1] = 0;
9495         }
9496       }
9497       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9498           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9499         /* [HGM] holdings: Add to holdings, if holdings exist */
9500         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9501                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9502                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9503         }
9504         p = (int) captured;
9505         if (p >= (int) BlackPawn) {
9506           p -= (int)BlackPawn;
9507           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9508                   /* in Shogi restore piece to its original  first */
9509                   captured = (ChessSquare) (DEMOTED captured);
9510                   p = DEMOTED p;
9511           }
9512           p = PieceToNumber((ChessSquare)p);
9513           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9514           board[p][BOARD_WIDTH-2]++;
9515           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9516         } else {
9517           p -= (int)WhitePawn;
9518           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9519                   captured = (ChessSquare) (DEMOTED captured);
9520                   p = DEMOTED p;
9521           }
9522           p = PieceToNumber((ChessSquare)p);
9523           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9524           board[BOARD_HEIGHT-1-p][1]++;
9525           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9526         }
9527       }
9528     } else if (gameInfo.variant == VariantAtomic) {
9529       if (captured != EmptySquare) {
9530         int y, x;
9531         for (y = toY-1; y <= toY+1; y++) {
9532           for (x = toX-1; x <= toX+1; x++) {
9533             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9534                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9535               board[y][x] = EmptySquare;
9536             }
9537           }
9538         }
9539         board[toY][toX] = EmptySquare;
9540       }
9541     }
9542     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9543         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9544     } else
9545     if(promoChar == '+') {
9546         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9547         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9548     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9549         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9550         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9551            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9552         board[toY][toX] = newPiece;
9553     }
9554     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9555                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9556         // [HGM] superchess: take promotion piece out of holdings
9557         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9558         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9559             if(!--board[k][BOARD_WIDTH-2])
9560                 board[k][BOARD_WIDTH-1] = EmptySquare;
9561         } else {
9562             if(!--board[BOARD_HEIGHT-1-k][1])
9563                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9564         }
9565     }
9566
9567 }
9568
9569 /* Updates forwardMostMove */
9570 void
9571 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9572 {
9573 //    forwardMostMove++; // [HGM] bare: moved downstream
9574
9575     (void) CoordsToAlgebraic(boards[forwardMostMove],
9576                              PosFlags(forwardMostMove),
9577                              fromY, fromX, toY, toX, promoChar,
9578                              parseList[forwardMostMove]);
9579
9580     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9581         int timeLeft; static int lastLoadFlag=0; int king, piece;
9582         piece = boards[forwardMostMove][fromY][fromX];
9583         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9584         if(gameInfo.variant == VariantKnightmate)
9585             king += (int) WhiteUnicorn - (int) WhiteKing;
9586         if(forwardMostMove == 0) {
9587             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9588                 fprintf(serverMoves, "%s;", UserName());
9589             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9590                 fprintf(serverMoves, "%s;", second.tidy);
9591             fprintf(serverMoves, "%s;", first.tidy);
9592             if(gameMode == MachinePlaysWhite)
9593                 fprintf(serverMoves, "%s;", UserName());
9594             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9595                 fprintf(serverMoves, "%s;", second.tidy);
9596         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9597         lastLoadFlag = loadFlag;
9598         // print base move
9599         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9600         // print castling suffix
9601         if( toY == fromY && piece == king ) {
9602             if(toX-fromX > 1)
9603                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9604             if(fromX-toX >1)
9605                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9606         }
9607         // e.p. suffix
9608         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9609              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9610              boards[forwardMostMove][toY][toX] == EmptySquare
9611              && fromX != toX && fromY != toY)
9612                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9613         // promotion suffix
9614         if(promoChar != NULLCHAR)
9615                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9616         if(!loadFlag) {
9617                 char buf[MOVE_LEN*2], *p; int len;
9618             fprintf(serverMoves, "/%d/%d",
9619                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9620             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9621             else                      timeLeft = blackTimeRemaining/1000;
9622             fprintf(serverMoves, "/%d", timeLeft);
9623                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9624                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9625                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9626             fprintf(serverMoves, "/%s", buf);
9627         }
9628         fflush(serverMoves);
9629     }
9630
9631     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9632         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9633       return;
9634     }
9635     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9636     if (commentList[forwardMostMove+1] != NULL) {
9637         free(commentList[forwardMostMove+1]);
9638         commentList[forwardMostMove+1] = NULL;
9639     }
9640     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9641     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9642     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9643     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9644     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9645     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9646     adjustedClock = FALSE;
9647     gameInfo.result = GameUnfinished;
9648     if (gameInfo.resultDetails != NULL) {
9649         free(gameInfo.resultDetails);
9650         gameInfo.resultDetails = NULL;
9651     }
9652     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9653                               moveList[forwardMostMove - 1]);
9654     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9655       case MT_NONE:
9656       case MT_STALEMATE:
9657       default:
9658         break;
9659       case MT_CHECK:
9660         if(gameInfo.variant != VariantShogi)
9661             strcat(parseList[forwardMostMove - 1], "+");
9662         break;
9663       case MT_CHECKMATE:
9664       case MT_STAINMATE:
9665         strcat(parseList[forwardMostMove - 1], "#");
9666         break;
9667     }
9668
9669 }
9670
9671 /* Updates currentMove if not pausing */
9672 void
9673 ShowMove (int fromX, int fromY, int toX, int toY)
9674 {
9675     int instant = (gameMode == PlayFromGameFile) ?
9676         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9677     if(appData.noGUI) return;
9678     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9679         if (!instant) {
9680             if (forwardMostMove == currentMove + 1) {
9681                 AnimateMove(boards[forwardMostMove - 1],
9682                             fromX, fromY, toX, toY);
9683             }
9684             if (appData.highlightLastMove) {
9685                 SetHighlights(fromX, fromY, toX, toY);
9686             }
9687         }
9688         currentMove = forwardMostMove;
9689     }
9690
9691     if (instant) return;
9692
9693     DisplayMove(currentMove - 1);
9694     DrawPosition(FALSE, boards[currentMove]);
9695     DisplayBothClocks();
9696     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9697 }
9698
9699 void
9700 SendEgtPath (ChessProgramState *cps)
9701 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9702         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9703
9704         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9705
9706         while(*p) {
9707             char c, *q = name+1, *r, *s;
9708
9709             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9710             while(*p && *p != ',') *q++ = *p++;
9711             *q++ = ':'; *q = 0;
9712             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9713                 strcmp(name, ",nalimov:") == 0 ) {
9714                 // take nalimov path from the menu-changeable option first, if it is defined
9715               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9716                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9717             } else
9718             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9719                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9720                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9721                 s = r = StrStr(s, ":") + 1; // beginning of path info
9722                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9723                 c = *r; *r = 0;             // temporarily null-terminate path info
9724                     *--q = 0;               // strip of trailig ':' from name
9725                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9726                 *r = c;
9727                 SendToProgram(buf,cps);     // send egtbpath command for this format
9728             }
9729             if(*p == ',') p++; // read away comma to position for next format name
9730         }
9731 }
9732
9733 void
9734 InitChessProgram (ChessProgramState *cps, int setup)
9735 /* setup needed to setup FRC opening position */
9736 {
9737     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9738     if (appData.noChessProgram) return;
9739     hintRequested = FALSE;
9740     bookRequested = FALSE;
9741
9742     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9743     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9744     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9745     if(cps->memSize) { /* [HGM] memory */
9746       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9747         SendToProgram(buf, cps);
9748     }
9749     SendEgtPath(cps); /* [HGM] EGT */
9750     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9751       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9752         SendToProgram(buf, cps);
9753     }
9754
9755     SendToProgram(cps->initString, cps);
9756     if (gameInfo.variant != VariantNormal &&
9757         gameInfo.variant != VariantLoadable
9758         /* [HGM] also send variant if board size non-standard */
9759         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9760                                             ) {
9761       char *v = VariantName(gameInfo.variant);
9762       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9763         /* [HGM] in protocol 1 we have to assume all variants valid */
9764         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9765         DisplayFatalError(buf, 0, 1);
9766         return;
9767       }
9768
9769       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9770       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9771       if( gameInfo.variant == VariantXiangqi )
9772            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9773       if( gameInfo.variant == VariantShogi )
9774            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9775       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9776            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9777       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9778           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9779            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9780       if( gameInfo.variant == VariantCourier )
9781            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9782       if( gameInfo.variant == VariantSuper )
9783            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9784       if( gameInfo.variant == VariantGreat )
9785            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9786       if( gameInfo.variant == VariantSChess )
9787            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9788       if( gameInfo.variant == VariantGrand )
9789            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9790
9791       if(overruled) {
9792         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9793                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9794            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9795            if(StrStr(cps->variants, b) == NULL) {
9796                // specific sized variant not known, check if general sizing allowed
9797                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9798                    if(StrStr(cps->variants, "boardsize") == NULL) {
9799                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9800                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9801                        DisplayFatalError(buf, 0, 1);
9802                        return;
9803                    }
9804                    /* [HGM] here we really should compare with the maximum supported board size */
9805                }
9806            }
9807       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9808       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9809       SendToProgram(buf, cps);
9810     }
9811     currentlyInitializedVariant = gameInfo.variant;
9812
9813     /* [HGM] send opening position in FRC to first engine */
9814     if(setup) {
9815           SendToProgram("force\n", cps);
9816           SendBoard(cps, 0);
9817           /* engine is now in force mode! Set flag to wake it up after first move. */
9818           setboardSpoiledMachineBlack = 1;
9819     }
9820
9821     if (cps->sendICS) {
9822       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9823       SendToProgram(buf, cps);
9824     }
9825     cps->maybeThinking = FALSE;
9826     cps->offeredDraw = 0;
9827     if (!appData.icsActive) {
9828         SendTimeControl(cps, movesPerSession, timeControl,
9829                         timeIncrement, appData.searchDepth,
9830                         searchTime);
9831     }
9832     if (appData.showThinking
9833         // [HGM] thinking: four options require thinking output to be sent
9834         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9835                                 ) {
9836         SendToProgram("post\n", cps);
9837     }
9838     SendToProgram("hard\n", cps);
9839     if (!appData.ponderNextMove) {
9840         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9841            it without being sure what state we are in first.  "hard"
9842            is not a toggle, so that one is OK.
9843          */
9844         SendToProgram("easy\n", cps);
9845     }
9846     if (cps->usePing) {
9847       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9848       SendToProgram(buf, cps);
9849     }
9850     cps->initDone = TRUE;
9851     ClearEngineOutputPane(cps == &second);
9852 }
9853
9854
9855 void
9856 StartChessProgram (ChessProgramState *cps)
9857 {
9858     char buf[MSG_SIZ];
9859     int err;
9860
9861     if (appData.noChessProgram) return;
9862     cps->initDone = FALSE;
9863
9864     if (strcmp(cps->host, "localhost") == 0) {
9865         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9866     } else if (*appData.remoteShell == NULLCHAR) {
9867         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9868     } else {
9869         if (*appData.remoteUser == NULLCHAR) {
9870           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9871                     cps->program);
9872         } else {
9873           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9874                     cps->host, appData.remoteUser, cps->program);
9875         }
9876         err = StartChildProcess(buf, "", &cps->pr);
9877     }
9878
9879     if (err != 0) {
9880       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9881         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9882         if(cps != &first) return;
9883         appData.noChessProgram = TRUE;
9884         ThawUI();
9885         SetNCPMode();
9886 //      DisplayFatalError(buf, err, 1);
9887 //      cps->pr = NoProc;
9888 //      cps->isr = NULL;
9889         return;
9890     }
9891
9892     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9893     if (cps->protocolVersion > 1) {
9894       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9895       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9896       cps->comboCnt = 0;  //                and values of combo boxes
9897       SendToProgram(buf, cps);
9898     } else {
9899       SendToProgram("xboard\n", cps);
9900     }
9901 }
9902
9903 void
9904 TwoMachinesEventIfReady P((void))
9905 {
9906   static int curMess = 0;
9907   if (first.lastPing != first.lastPong) {
9908     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9909     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9910     return;
9911   }
9912   if (second.lastPing != second.lastPong) {
9913     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9914     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9915     return;
9916   }
9917   DisplayMessage("", ""); curMess = 0;
9918   ThawUI();
9919   TwoMachinesEvent();
9920 }
9921
9922 char *
9923 MakeName (char *template)
9924 {
9925     time_t clock;
9926     struct tm *tm;
9927     static char buf[MSG_SIZ];
9928     char *p = buf;
9929     int i;
9930
9931     clock = time((time_t *)NULL);
9932     tm = localtime(&clock);
9933
9934     while(*p++ = *template++) if(p[-1] == '%') {
9935         switch(*template++) {
9936           case 0:   *p = 0; return buf;
9937           case 'Y': i = tm->tm_year+1900; break;
9938           case 'y': i = tm->tm_year-100; break;
9939           case 'M': i = tm->tm_mon+1; break;
9940           case 'd': i = tm->tm_mday; break;
9941           case 'h': i = tm->tm_hour; break;
9942           case 'm': i = tm->tm_min; break;
9943           case 's': i = tm->tm_sec; break;
9944           default:  i = 0;
9945         }
9946         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9947     }
9948     return buf;
9949 }
9950
9951 int
9952 CountPlayers (char *p)
9953 {
9954     int n = 0;
9955     while(p = strchr(p, '\n')) p++, n++; // count participants
9956     return n;
9957 }
9958
9959 FILE *
9960 WriteTourneyFile (char *results, FILE *f)
9961 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9962     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9963     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9964         // create a file with tournament description
9965         fprintf(f, "-participants {%s}\n", appData.participants);
9966         fprintf(f, "-seedBase %d\n", appData.seedBase);
9967         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9968         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9969         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9970         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9971         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9972         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9973         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9974         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9975         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9976         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9977         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9978         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9979         if(searchTime > 0)
9980                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9981         else {
9982                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9983                 fprintf(f, "-tc %s\n", appData.timeControl);
9984                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9985         }
9986         fprintf(f, "-results \"%s\"\n", results);
9987     }
9988     return f;
9989 }
9990
9991 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9992
9993 void
9994 Substitute (char *participants, int expunge)
9995 {
9996     int i, changed, changes=0, nPlayers=0;
9997     char *p, *q, *r, buf[MSG_SIZ];
9998     if(participants == NULL) return;
9999     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10000     r = p = participants; q = appData.participants;
10001     while(*p && *p == *q) {
10002         if(*p == '\n') r = p+1, nPlayers++;
10003         p++; q++;
10004     }
10005     if(*p) { // difference
10006         while(*p && *p++ != '\n');
10007         while(*q && *q++ != '\n');
10008       changed = nPlayers;
10009         changes = 1 + (strcmp(p, q) != 0);
10010     }
10011     if(changes == 1) { // a single engine mnemonic was changed
10012         q = r; while(*q) nPlayers += (*q++ == '\n');
10013         p = buf; while(*r && (*p = *r++) != '\n') p++;
10014         *p = NULLCHAR;
10015         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10016         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10017         if(mnemonic[i]) { // The substitute is valid
10018             FILE *f;
10019             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10020                 flock(fileno(f), LOCK_EX);
10021                 ParseArgsFromFile(f);
10022                 fseek(f, 0, SEEK_SET);
10023                 FREE(appData.participants); appData.participants = participants;
10024                 if(expunge) { // erase results of replaced engine
10025                     int len = strlen(appData.results), w, b, dummy;
10026                     for(i=0; i<len; i++) {
10027                         Pairing(i, nPlayers, &w, &b, &dummy);
10028                         if((w == changed || b == changed) && appData.results[i] == '*') {
10029                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10030                             fclose(f);
10031                             return;
10032                         }
10033                     }
10034                     for(i=0; i<len; i++) {
10035                         Pairing(i, nPlayers, &w, &b, &dummy);
10036                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10037                     }
10038                 }
10039                 WriteTourneyFile(appData.results, f);
10040                 fclose(f); // release lock
10041                 return;
10042             }
10043         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10044     }
10045     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10046     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10047     free(participants);
10048     return;
10049 }
10050
10051 int
10052 CreateTourney (char *name)
10053 {
10054         FILE *f;
10055         if(matchMode && strcmp(name, appData.tourneyFile)) {
10056              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10057         }
10058         if(name[0] == NULLCHAR) {
10059             if(appData.participants[0])
10060                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10061             return 0;
10062         }
10063         f = fopen(name, "r");
10064         if(f) { // file exists
10065             ASSIGN(appData.tourneyFile, name);
10066             ParseArgsFromFile(f); // parse it
10067         } else {
10068             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10069             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10070                 DisplayError(_("Not enough participants"), 0);
10071                 return 0;
10072             }
10073             ASSIGN(appData.tourneyFile, name);
10074             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10075             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10076         }
10077         fclose(f);
10078         appData.noChessProgram = FALSE;
10079         appData.clockMode = TRUE;
10080         SetGNUMode();
10081         return 1;
10082 }
10083
10084 int
10085 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10086 {
10087     char buf[MSG_SIZ], *p, *q;
10088     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10089     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10090     skip = !all && group[0]; // if group requested, we start in skip mode
10091     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10092         p = names; q = buf; header = 0;
10093         while(*p && *p != '\n') *q++ = *p++;
10094         *q = 0;
10095         if(*p == '\n') p++;
10096         if(buf[0] == '#') {
10097             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10098             depth++; // we must be entering a new group
10099             if(all) continue; // suppress printing group headers when complete list requested
10100             header = 1;
10101             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10102         }
10103         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10104         if(engineList[i]) free(engineList[i]);
10105         engineList[i] = strdup(buf);
10106         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10107         if(engineMnemonic[i]) free(engineMnemonic[i]);
10108         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10109             strcat(buf, " (");
10110             sscanf(q + 8, "%s", buf + strlen(buf));
10111             strcat(buf, ")");
10112         }
10113         engineMnemonic[i] = strdup(buf);
10114         i++;
10115     }
10116     engineList[i] = engineMnemonic[i] = NULL;
10117     return i;
10118 }
10119
10120 // following implemented as macro to avoid type limitations
10121 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10122
10123 void
10124 SwapEngines (int n)
10125 {   // swap settings for first engine and other engine (so far only some selected options)
10126     int h;
10127     char *p;
10128     if(n == 0) return;
10129     SWAP(directory, p)
10130     SWAP(chessProgram, p)
10131     SWAP(isUCI, h)
10132     SWAP(hasOwnBookUCI, h)
10133     SWAP(protocolVersion, h)
10134     SWAP(reuse, h)
10135     SWAP(scoreIsAbsolute, h)
10136     SWAP(timeOdds, h)
10137     SWAP(logo, p)
10138     SWAP(pgnName, p)
10139     SWAP(pvSAN, h)
10140     SWAP(engOptions, p)
10141     SWAP(engInitString, p)
10142     SWAP(computerString, p)
10143     SWAP(features, p)
10144     SWAP(fenOverride, p)
10145     SWAP(NPS, h)
10146     SWAP(accumulateTC, h)
10147     SWAP(host, p)
10148 }
10149
10150 int
10151 SetPlayer (int player, char *p)
10152 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10153     int i;
10154     char buf[MSG_SIZ], *engineName;
10155     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10156     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10157     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10158     if(mnemonic[i]) {
10159         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10160         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10161         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10162         ParseArgsFromString(buf);
10163     }
10164     free(engineName);
10165     return i;
10166 }
10167
10168 char *recentEngines;
10169
10170 void
10171 RecentEngineEvent (int nr)
10172 {
10173     int n;
10174 //    SwapEngines(1); // bump first to second
10175 //    ReplaceEngine(&second, 1); // and load it there
10176     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10177     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10178     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10179         ReplaceEngine(&first, 0);
10180         FloatToFront(&appData.recentEngineList, command[n]);
10181     }
10182 }
10183
10184 int
10185 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10186 {   // determine players from game number
10187     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10188
10189     if(appData.tourneyType == 0) {
10190         roundsPerCycle = (nPlayers - 1) | 1;
10191         pairingsPerRound = nPlayers / 2;
10192     } else if(appData.tourneyType > 0) {
10193         roundsPerCycle = nPlayers - appData.tourneyType;
10194         pairingsPerRound = appData.tourneyType;
10195     }
10196     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10197     gamesPerCycle = gamesPerRound * roundsPerCycle;
10198     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10199     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10200     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10201     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10202     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10203     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10204
10205     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10206     if(appData.roundSync) *syncInterval = gamesPerRound;
10207
10208     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10209
10210     if(appData.tourneyType == 0) {
10211         if(curPairing == (nPlayers-1)/2 ) {
10212             *whitePlayer = curRound;
10213             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10214         } else {
10215             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10216             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10217             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10218             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10219         }
10220     } else if(appData.tourneyType > 1) {
10221         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10222         *whitePlayer = curRound + appData.tourneyType;
10223     } else if(appData.tourneyType > 0) {
10224         *whitePlayer = curPairing;
10225         *blackPlayer = curRound + appData.tourneyType;
10226     }
10227
10228     // take care of white/black alternation per round. 
10229     // For cycles and games this is already taken care of by default, derived from matchGame!
10230     return curRound & 1;
10231 }
10232
10233 int
10234 NextTourneyGame (int nr, int *swapColors)
10235 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10236     char *p, *q;
10237     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10238     FILE *tf;
10239     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10240     tf = fopen(appData.tourneyFile, "r");
10241     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10242     ParseArgsFromFile(tf); fclose(tf);
10243     InitTimeControls(); // TC might be altered from tourney file
10244
10245     nPlayers = CountPlayers(appData.participants); // count participants
10246     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10247     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10248
10249     if(syncInterval) {
10250         p = q = appData.results;
10251         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10252         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10253             DisplayMessage(_("Waiting for other game(s)"),"");
10254             waitingForGame = TRUE;
10255             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10256             return 0;
10257         }
10258         waitingForGame = FALSE;
10259     }
10260
10261     if(appData.tourneyType < 0) {
10262         if(nr>=0 && !pairingReceived) {
10263             char buf[1<<16];
10264             if(pairing.pr == NoProc) {
10265                 if(!appData.pairingEngine[0]) {
10266                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10267                     return 0;
10268                 }
10269                 StartChessProgram(&pairing); // starts the pairing engine
10270             }
10271             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10272             SendToProgram(buf, &pairing);
10273             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10274             SendToProgram(buf, &pairing);
10275             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10276         }
10277         pairingReceived = 0;                              // ... so we continue here 
10278         *swapColors = 0;
10279         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10280         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10281         matchGame = 1; roundNr = nr / syncInterval + 1;
10282     }
10283
10284     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10285
10286     // redefine engines, engine dir, etc.
10287     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10288     if(first.pr == NoProc) {
10289       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10290       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10291     }
10292     if(second.pr == NoProc) {
10293       SwapEngines(1);
10294       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10295       SwapEngines(1);         // and make that valid for second engine by swapping
10296       InitEngine(&second, 1);
10297     }
10298     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10299     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10300     return 1;
10301 }
10302
10303 void
10304 NextMatchGame ()
10305 {   // performs game initialization that does not invoke engines, and then tries to start the game
10306     int res, firstWhite, swapColors = 0;
10307     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10308     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
10309         char buf[MSG_SIZ];
10310         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10311         if(strcmp(buf, currentDebugFile)) { // name has changed
10312             FILE *f = fopen(buf, "w");
10313             if(f) { // if opening the new file failed, just keep using the old one
10314                 ASSIGN(currentDebugFile, buf);
10315                 fclose(debugFP);
10316                 debugFP = f;
10317             }
10318             if(appData.serverFileName) {
10319                 if(serverFP) fclose(serverFP);
10320                 serverFP = fopen(appData.serverFileName, "w");
10321                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10322                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10323             }
10324         }
10325     }
10326     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10327     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10328     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10329     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10330     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10331     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10332     Reset(FALSE, first.pr != NoProc);
10333     res = LoadGameOrPosition(matchGame); // setup game
10334     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10335     if(!res) return; // abort when bad game/pos file
10336     TwoMachinesEvent();
10337 }
10338
10339 void
10340 UserAdjudicationEvent (int result)
10341 {
10342     ChessMove gameResult = GameIsDrawn;
10343
10344     if( result > 0 ) {
10345         gameResult = WhiteWins;
10346     }
10347     else if( result < 0 ) {
10348         gameResult = BlackWins;
10349     }
10350
10351     if( gameMode == TwoMachinesPlay ) {
10352         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10353     }
10354 }
10355
10356
10357 // [HGM] save: calculate checksum of game to make games easily identifiable
10358 int
10359 StringCheckSum (char *s)
10360 {
10361         int i = 0;
10362         if(s==NULL) return 0;
10363         while(*s) i = i*259 + *s++;
10364         return i;
10365 }
10366
10367 int
10368 GameCheckSum ()
10369 {
10370         int i, sum=0;
10371         for(i=backwardMostMove; i<forwardMostMove; i++) {
10372                 sum += pvInfoList[i].depth;
10373                 sum += StringCheckSum(parseList[i]);
10374                 sum += StringCheckSum(commentList[i]);
10375                 sum *= 261;
10376         }
10377         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10378         return sum + StringCheckSum(commentList[i]);
10379 } // end of save patch
10380
10381 void
10382 GameEnds (ChessMove result, char *resultDetails, int whosays)
10383 {
10384     GameMode nextGameMode;
10385     int isIcsGame;
10386     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10387
10388     if(endingGame) return; /* [HGM] crash: forbid recursion */
10389     endingGame = 1;
10390     if(twoBoards) { // [HGM] dual: switch back to one board
10391         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10392         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10393     }
10394     if (appData.debugMode) {
10395       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10396               result, resultDetails ? resultDetails : "(null)", whosays);
10397     }
10398
10399     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10400
10401     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10402         /* If we are playing on ICS, the server decides when the
10403            game is over, but the engine can offer to draw, claim
10404            a draw, or resign.
10405          */
10406 #if ZIPPY
10407         if (appData.zippyPlay && first.initDone) {
10408             if (result == GameIsDrawn) {
10409                 /* In case draw still needs to be claimed */
10410                 SendToICS(ics_prefix);
10411                 SendToICS("draw\n");
10412             } else if (StrCaseStr(resultDetails, "resign")) {
10413                 SendToICS(ics_prefix);
10414                 SendToICS("resign\n");
10415             }
10416         }
10417 #endif
10418         endingGame = 0; /* [HGM] crash */
10419         return;
10420     }
10421
10422     /* If we're loading the game from a file, stop */
10423     if (whosays == GE_FILE) {
10424       (void) StopLoadGameTimer();
10425       gameFileFP = NULL;
10426     }
10427
10428     /* Cancel draw offers */
10429     first.offeredDraw = second.offeredDraw = 0;
10430
10431     /* If this is an ICS game, only ICS can really say it's done;
10432        if not, anyone can. */
10433     isIcsGame = (gameMode == IcsPlayingWhite ||
10434                  gameMode == IcsPlayingBlack ||
10435                  gameMode == IcsObserving    ||
10436                  gameMode == IcsExamining);
10437
10438     if (!isIcsGame || whosays == GE_ICS) {
10439         /* OK -- not an ICS game, or ICS said it was done */
10440         StopClocks();
10441         if (!isIcsGame && !appData.noChessProgram)
10442           SetUserThinkingEnables();
10443
10444         /* [HGM] if a machine claims the game end we verify this claim */
10445         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10446             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10447                 char claimer;
10448                 ChessMove trueResult = (ChessMove) -1;
10449
10450                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10451                                             first.twoMachinesColor[0] :
10452                                             second.twoMachinesColor[0] ;
10453
10454                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10455                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10456                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10457                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10458                 } else
10459                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10460                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10461                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10462                 } else
10463                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10464                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10465                 }
10466
10467                 // now verify win claims, but not in drop games, as we don't understand those yet
10468                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10469                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10470                     (result == WhiteWins && claimer == 'w' ||
10471                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10472                       if (appData.debugMode) {
10473                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10474                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10475                       }
10476                       if(result != trueResult) {
10477                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10478                               result = claimer == 'w' ? BlackWins : WhiteWins;
10479                               resultDetails = buf;
10480                       }
10481                 } else
10482                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10483                     && (forwardMostMove <= backwardMostMove ||
10484                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10485                         (claimer=='b')==(forwardMostMove&1))
10486                                                                                   ) {
10487                       /* [HGM] verify: draws that were not flagged are false claims */
10488                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10489                       result = claimer == 'w' ? BlackWins : WhiteWins;
10490                       resultDetails = buf;
10491                 }
10492                 /* (Claiming a loss is accepted no questions asked!) */
10493             }
10494             /* [HGM] bare: don't allow bare King to win */
10495             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10496                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10497                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10498                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10499                && result != GameIsDrawn)
10500             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10501                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10502                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10503                         if(p >= 0 && p <= (int)WhiteKing) k++;
10504                 }
10505                 if (appData.debugMode) {
10506                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10507                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10508                 }
10509                 if(k <= 1) {
10510                         result = GameIsDrawn;
10511                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10512                         resultDetails = buf;
10513                 }
10514             }
10515         }
10516
10517
10518         if(serverMoves != NULL && !loadFlag) { char c = '=';
10519             if(result==WhiteWins) c = '+';
10520             if(result==BlackWins) c = '-';
10521             if(resultDetails != NULL)
10522                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10523         }
10524         if (resultDetails != NULL) {
10525             gameInfo.result = result;
10526             gameInfo.resultDetails = StrSave(resultDetails);
10527
10528             /* display last move only if game was not loaded from file */
10529             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10530                 DisplayMove(currentMove - 1);
10531
10532             if (forwardMostMove != 0) {
10533                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10534                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10535                                                                 ) {
10536                     if (*appData.saveGameFile != NULLCHAR) {
10537                         SaveGameToFile(appData.saveGameFile, TRUE);
10538                     } else if (appData.autoSaveGames) {
10539                         AutoSaveGame();
10540                     }
10541                     if (*appData.savePositionFile != NULLCHAR) {
10542                         SavePositionToFile(appData.savePositionFile);
10543                     }
10544                 }
10545             }
10546
10547             /* Tell program how game ended in case it is learning */
10548             /* [HGM] Moved this to after saving the PGN, just in case */
10549             /* engine died and we got here through time loss. In that */
10550             /* case we will get a fatal error writing the pipe, which */
10551             /* would otherwise lose us the PGN.                       */
10552             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10553             /* output during GameEnds should never be fatal anymore   */
10554             if (gameMode == MachinePlaysWhite ||
10555                 gameMode == MachinePlaysBlack ||
10556                 gameMode == TwoMachinesPlay ||
10557                 gameMode == IcsPlayingWhite ||
10558                 gameMode == IcsPlayingBlack ||
10559                 gameMode == BeginningOfGame) {
10560                 char buf[MSG_SIZ];
10561                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10562                         resultDetails);
10563                 if (first.pr != NoProc) {
10564                     SendToProgram(buf, &first);
10565                 }
10566                 if (second.pr != NoProc &&
10567                     gameMode == TwoMachinesPlay) {
10568                     SendToProgram(buf, &second);
10569                 }
10570             }
10571         }
10572
10573         if (appData.icsActive) {
10574             if (appData.quietPlay &&
10575                 (gameMode == IcsPlayingWhite ||
10576                  gameMode == IcsPlayingBlack)) {
10577                 SendToICS(ics_prefix);
10578                 SendToICS("set shout 1\n");
10579             }
10580             nextGameMode = IcsIdle;
10581             ics_user_moved = FALSE;
10582             /* clean up premove.  It's ugly when the game has ended and the
10583              * premove highlights are still on the board.
10584              */
10585             if (gotPremove) {
10586               gotPremove = FALSE;
10587               ClearPremoveHighlights();
10588               DrawPosition(FALSE, boards[currentMove]);
10589             }
10590             if (whosays == GE_ICS) {
10591                 switch (result) {
10592                 case WhiteWins:
10593                     if (gameMode == IcsPlayingWhite)
10594                         PlayIcsWinSound();
10595                     else if(gameMode == IcsPlayingBlack)
10596                         PlayIcsLossSound();
10597                     break;
10598                 case BlackWins:
10599                     if (gameMode == IcsPlayingBlack)
10600                         PlayIcsWinSound();
10601                     else if(gameMode == IcsPlayingWhite)
10602                         PlayIcsLossSound();
10603                     break;
10604                 case GameIsDrawn:
10605                     PlayIcsDrawSound();
10606                     break;
10607                 default:
10608                     PlayIcsUnfinishedSound();
10609                 }
10610             }
10611         } else if (gameMode == EditGame ||
10612                    gameMode == PlayFromGameFile ||
10613                    gameMode == AnalyzeMode ||
10614                    gameMode == AnalyzeFile) {
10615             nextGameMode = gameMode;
10616         } else {
10617             nextGameMode = EndOfGame;
10618         }
10619         pausing = FALSE;
10620         ModeHighlight();
10621     } else {
10622         nextGameMode = gameMode;
10623     }
10624
10625     if (appData.noChessProgram) {
10626         gameMode = nextGameMode;
10627         ModeHighlight();
10628         endingGame = 0; /* [HGM] crash */
10629         return;
10630     }
10631
10632     if (first.reuse) {
10633         /* Put first chess program into idle state */
10634         if (first.pr != NoProc &&
10635             (gameMode == MachinePlaysWhite ||
10636              gameMode == MachinePlaysBlack ||
10637              gameMode == TwoMachinesPlay ||
10638              gameMode == IcsPlayingWhite ||
10639              gameMode == IcsPlayingBlack ||
10640              gameMode == BeginningOfGame)) {
10641             SendToProgram("force\n", &first);
10642             if (first.usePing) {
10643               char buf[MSG_SIZ];
10644               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10645               SendToProgram(buf, &first);
10646             }
10647         }
10648     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10649         /* Kill off first chess program */
10650         if (first.isr != NULL)
10651           RemoveInputSource(first.isr);
10652         first.isr = NULL;
10653
10654         if (first.pr != NoProc) {
10655             ExitAnalyzeMode();
10656             DoSleep( appData.delayBeforeQuit );
10657             SendToProgram("quit\n", &first);
10658             DoSleep( appData.delayAfterQuit );
10659             DestroyChildProcess(first.pr, first.useSigterm);
10660         }
10661         first.pr = NoProc;
10662     }
10663     if (second.reuse) {
10664         /* Put second chess program into idle state */
10665         if (second.pr != NoProc &&
10666             gameMode == TwoMachinesPlay) {
10667             SendToProgram("force\n", &second);
10668             if (second.usePing) {
10669               char buf[MSG_SIZ];
10670               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10671               SendToProgram(buf, &second);
10672             }
10673         }
10674     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10675         /* Kill off second chess program */
10676         if (second.isr != NULL)
10677           RemoveInputSource(second.isr);
10678         second.isr = NULL;
10679
10680         if (second.pr != NoProc) {
10681             DoSleep( appData.delayBeforeQuit );
10682             SendToProgram("quit\n", &second);
10683             DoSleep( appData.delayAfterQuit );
10684             DestroyChildProcess(second.pr, second.useSigterm);
10685         }
10686         second.pr = NoProc;
10687     }
10688
10689     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10690         char resChar = '=';
10691         switch (result) {
10692         case WhiteWins:
10693           resChar = '+';
10694           if (first.twoMachinesColor[0] == 'w') {
10695             first.matchWins++;
10696           } else {
10697             second.matchWins++;
10698           }
10699           break;
10700         case BlackWins:
10701           resChar = '-';
10702           if (first.twoMachinesColor[0] == 'b') {
10703             first.matchWins++;
10704           } else {
10705             second.matchWins++;
10706           }
10707           break;
10708         case GameUnfinished:
10709           resChar = ' ';
10710         default:
10711           break;
10712         }
10713
10714         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10715         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10716             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10717             ReserveGame(nextGame, resChar); // sets nextGame
10718             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10719             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10720         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10721
10722         if (nextGame <= appData.matchGames && !abortMatch) {
10723             gameMode = nextGameMode;
10724             matchGame = nextGame; // this will be overruled in tourney mode!
10725             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10726             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10727             endingGame = 0; /* [HGM] crash */
10728             return;
10729         } else {
10730             gameMode = nextGameMode;
10731             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10732                      first.tidy, second.tidy,
10733                      first.matchWins, second.matchWins,
10734                      appData.matchGames - (first.matchWins + second.matchWins));
10735             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10736             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10737             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10738             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10739                 first.twoMachinesColor = "black\n";
10740                 second.twoMachinesColor = "white\n";
10741             } else {
10742                 first.twoMachinesColor = "white\n";
10743                 second.twoMachinesColor = "black\n";
10744             }
10745         }
10746     }
10747     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10748         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10749       ExitAnalyzeMode();
10750     gameMode = nextGameMode;
10751     ModeHighlight();
10752     endingGame = 0;  /* [HGM] crash */
10753     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10754         if(matchMode == TRUE) { // match through command line: exit with or without popup
10755             if(ranking) {
10756                 ToNrEvent(forwardMostMove);
10757                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10758                 else ExitEvent(0);
10759             } else DisplayFatalError(buf, 0, 0);
10760         } else { // match through menu; just stop, with or without popup
10761             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10762             ModeHighlight();
10763             if(ranking){
10764                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10765             } else DisplayNote(buf);
10766       }
10767       if(ranking) free(ranking);
10768     }
10769 }
10770
10771 /* Assumes program was just initialized (initString sent).
10772    Leaves program in force mode. */
10773 void
10774 FeedMovesToProgram (ChessProgramState *cps, int upto)
10775 {
10776     int i;
10777
10778     if (appData.debugMode)
10779       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10780               startedFromSetupPosition ? "position and " : "",
10781               backwardMostMove, upto, cps->which);
10782     if(currentlyInitializedVariant != gameInfo.variant) {
10783       char buf[MSG_SIZ];
10784         // [HGM] variantswitch: make engine aware of new variant
10785         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10786                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10787         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10788         SendToProgram(buf, cps);
10789         currentlyInitializedVariant = gameInfo.variant;
10790     }
10791     SendToProgram("force\n", cps);
10792     if (startedFromSetupPosition) {
10793         SendBoard(cps, backwardMostMove);
10794     if (appData.debugMode) {
10795         fprintf(debugFP, "feedMoves\n");
10796     }
10797     }
10798     for (i = backwardMostMove; i < upto; i++) {
10799         SendMoveToProgram(i, cps);
10800     }
10801 }
10802
10803
10804 int
10805 ResurrectChessProgram ()
10806 {
10807      /* The chess program may have exited.
10808         If so, restart it and feed it all the moves made so far. */
10809     static int doInit = 0;
10810
10811     if (appData.noChessProgram) return 1;
10812
10813     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10814         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10815         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10816         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10817     } else {
10818         if (first.pr != NoProc) return 1;
10819         StartChessProgram(&first);
10820     }
10821     InitChessProgram(&first, FALSE);
10822     FeedMovesToProgram(&first, currentMove);
10823
10824     if (!first.sendTime) {
10825         /* can't tell gnuchess what its clock should read,
10826            so we bow to its notion. */
10827         ResetClocks();
10828         timeRemaining[0][currentMove] = whiteTimeRemaining;
10829         timeRemaining[1][currentMove] = blackTimeRemaining;
10830     }
10831
10832     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10833                 appData.icsEngineAnalyze) && first.analysisSupport) {
10834       SendToProgram("analyze\n", &first);
10835       first.analyzing = TRUE;
10836     }
10837     return 1;
10838 }
10839
10840 /*
10841  * Button procedures
10842  */
10843 void
10844 Reset (int redraw, int init)
10845 {
10846     int i;
10847
10848     if (appData.debugMode) {
10849         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10850                 redraw, init, gameMode);
10851     }
10852     CleanupTail(); // [HGM] vari: delete any stored variations
10853     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10854     pausing = pauseExamInvalid = FALSE;
10855     startedFromSetupPosition = blackPlaysFirst = FALSE;
10856     firstMove = TRUE;
10857     whiteFlag = blackFlag = FALSE;
10858     userOfferedDraw = FALSE;
10859     hintRequested = bookRequested = FALSE;
10860     first.maybeThinking = FALSE;
10861     second.maybeThinking = FALSE;
10862     first.bookSuspend = FALSE; // [HGM] book
10863     second.bookSuspend = FALSE;
10864     thinkOutput[0] = NULLCHAR;
10865     lastHint[0] = NULLCHAR;
10866     ClearGameInfo(&gameInfo);
10867     gameInfo.variant = StringToVariant(appData.variant);
10868     ics_user_moved = ics_clock_paused = FALSE;
10869     ics_getting_history = H_FALSE;
10870     ics_gamenum = -1;
10871     white_holding[0] = black_holding[0] = NULLCHAR;
10872     ClearProgramStats();
10873     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10874
10875     ResetFrontEnd();
10876     ClearHighlights();
10877     flipView = appData.flipView;
10878     ClearPremoveHighlights();
10879     gotPremove = FALSE;
10880     alarmSounded = FALSE;
10881
10882     GameEnds(EndOfFile, NULL, GE_PLAYER);
10883     if(appData.serverMovesName != NULL) {
10884         /* [HGM] prepare to make moves file for broadcasting */
10885         clock_t t = clock();
10886         if(serverMoves != NULL) fclose(serverMoves);
10887         serverMoves = fopen(appData.serverMovesName, "r");
10888         if(serverMoves != NULL) {
10889             fclose(serverMoves);
10890             /* delay 15 sec before overwriting, so all clients can see end */
10891             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10892         }
10893         serverMoves = fopen(appData.serverMovesName, "w");
10894     }
10895
10896     ExitAnalyzeMode();
10897     gameMode = BeginningOfGame;
10898     ModeHighlight();
10899     if(appData.icsActive) gameInfo.variant = VariantNormal;
10900     currentMove = forwardMostMove = backwardMostMove = 0;
10901     MarkTargetSquares(1);
10902     InitPosition(redraw);
10903     for (i = 0; i < MAX_MOVES; i++) {
10904         if (commentList[i] != NULL) {
10905             free(commentList[i]);
10906             commentList[i] = NULL;
10907         }
10908     }
10909     ResetClocks();
10910     timeRemaining[0][0] = whiteTimeRemaining;
10911     timeRemaining[1][0] = blackTimeRemaining;
10912
10913     if (first.pr == NoProc) {
10914         StartChessProgram(&first);
10915     }
10916     if (init) {
10917             InitChessProgram(&first, startedFromSetupPosition);
10918     }
10919     DisplayTitle("");
10920     DisplayMessage("", "");
10921     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10922     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10923     ClearMap();        // [HGM] exclude: invalidate map
10924 }
10925
10926 void
10927 AutoPlayGameLoop ()
10928 {
10929     for (;;) {
10930         if (!AutoPlayOneMove())
10931           return;
10932         if (matchMode || appData.timeDelay == 0)
10933           continue;
10934         if (appData.timeDelay < 0)
10935           return;
10936         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
10937         break;
10938     }
10939 }
10940
10941
10942 int
10943 AutoPlayOneMove ()
10944 {
10945     int fromX, fromY, toX, toY;
10946
10947     if (appData.debugMode) {
10948       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10949     }
10950
10951     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10952       return FALSE;
10953
10954     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10955       pvInfoList[currentMove].depth = programStats.depth;
10956       pvInfoList[currentMove].score = programStats.score;
10957       pvInfoList[currentMove].time  = 0;
10958       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10959     }
10960
10961     if (currentMove >= forwardMostMove) {
10962       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10963 //      gameMode = EndOfGame;
10964 //      ModeHighlight();
10965
10966       /* [AS] Clear current move marker at the end of a game */
10967       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10968
10969       return FALSE;
10970     }
10971
10972     toX = moveList[currentMove][2] - AAA;
10973     toY = moveList[currentMove][3] - ONE;
10974
10975     if (moveList[currentMove][1] == '@') {
10976         if (appData.highlightLastMove) {
10977             SetHighlights(-1, -1, toX, toY);
10978         }
10979     } else {
10980         fromX = moveList[currentMove][0] - AAA;
10981         fromY = moveList[currentMove][1] - ONE;
10982
10983         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10984
10985         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10986
10987         if (appData.highlightLastMove) {
10988             SetHighlights(fromX, fromY, toX, toY);
10989         }
10990     }
10991     DisplayMove(currentMove);
10992     SendMoveToProgram(currentMove++, &first);
10993     DisplayBothClocks();
10994     DrawPosition(FALSE, boards[currentMove]);
10995     // [HGM] PV info: always display, routine tests if empty
10996     DisplayComment(currentMove - 1, commentList[currentMove]);
10997     return TRUE;
10998 }
10999
11000
11001 int
11002 LoadGameOneMove (ChessMove readAhead)
11003 {
11004     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11005     char promoChar = NULLCHAR;
11006     ChessMove moveType;
11007     char move[MSG_SIZ];
11008     char *p, *q;
11009
11010     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11011         gameMode != AnalyzeMode && gameMode != Training) {
11012         gameFileFP = NULL;
11013         return FALSE;
11014     }
11015
11016     yyboardindex = forwardMostMove;
11017     if (readAhead != EndOfFile) {
11018       moveType = readAhead;
11019     } else {
11020       if (gameFileFP == NULL)
11021           return FALSE;
11022       moveType = (ChessMove) Myylex();
11023     }
11024
11025     done = FALSE;
11026     switch (moveType) {
11027       case Comment:
11028         if (appData.debugMode)
11029           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11030         p = yy_text;
11031
11032         /* append the comment but don't display it */
11033         AppendComment(currentMove, p, FALSE);
11034         return TRUE;
11035
11036       case WhiteCapturesEnPassant:
11037       case BlackCapturesEnPassant:
11038       case WhitePromotion:
11039       case BlackPromotion:
11040       case WhiteNonPromotion:
11041       case BlackNonPromotion:
11042       case NormalMove:
11043       case WhiteKingSideCastle:
11044       case WhiteQueenSideCastle:
11045       case BlackKingSideCastle:
11046       case BlackQueenSideCastle:
11047       case WhiteKingSideCastleWild:
11048       case WhiteQueenSideCastleWild:
11049       case BlackKingSideCastleWild:
11050       case BlackQueenSideCastleWild:
11051       /* PUSH Fabien */
11052       case WhiteHSideCastleFR:
11053       case WhiteASideCastleFR:
11054       case BlackHSideCastleFR:
11055       case BlackASideCastleFR:
11056       /* POP Fabien */
11057         if (appData.debugMode)
11058           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11059         fromX = currentMoveString[0] - AAA;
11060         fromY = currentMoveString[1] - ONE;
11061         toX = currentMoveString[2] - AAA;
11062         toY = currentMoveString[3] - ONE;
11063         promoChar = currentMoveString[4];
11064         break;
11065
11066       case WhiteDrop:
11067       case BlackDrop:
11068         if (appData.debugMode)
11069           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11070         fromX = moveType == WhiteDrop ?
11071           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11072         (int) CharToPiece(ToLower(currentMoveString[0]));
11073         fromY = DROP_RANK;
11074         toX = currentMoveString[2] - AAA;
11075         toY = currentMoveString[3] - ONE;
11076         break;
11077
11078       case WhiteWins:
11079       case BlackWins:
11080       case GameIsDrawn:
11081       case GameUnfinished:
11082         if (appData.debugMode)
11083           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11084         p = strchr(yy_text, '{');
11085         if (p == NULL) p = strchr(yy_text, '(');
11086         if (p == NULL) {
11087             p = yy_text;
11088             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11089         } else {
11090             q = strchr(p, *p == '{' ? '}' : ')');
11091             if (q != NULL) *q = NULLCHAR;
11092             p++;
11093         }
11094         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11095         GameEnds(moveType, p, GE_FILE);
11096         done = TRUE;
11097         if (cmailMsgLoaded) {
11098             ClearHighlights();
11099             flipView = WhiteOnMove(currentMove);
11100             if (moveType == GameUnfinished) flipView = !flipView;
11101             if (appData.debugMode)
11102               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11103         }
11104         break;
11105
11106       case EndOfFile:
11107         if (appData.debugMode)
11108           fprintf(debugFP, "Parser hit end of file\n");
11109         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11110           case MT_NONE:
11111           case MT_CHECK:
11112             break;
11113           case MT_CHECKMATE:
11114           case MT_STAINMATE:
11115             if (WhiteOnMove(currentMove)) {
11116                 GameEnds(BlackWins, "Black mates", GE_FILE);
11117             } else {
11118                 GameEnds(WhiteWins, "White mates", GE_FILE);
11119             }
11120             break;
11121           case MT_STALEMATE:
11122             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11123             break;
11124         }
11125         done = TRUE;
11126         break;
11127
11128       case MoveNumberOne:
11129         if (lastLoadGameStart == GNUChessGame) {
11130             /* GNUChessGames have numbers, but they aren't move numbers */
11131             if (appData.debugMode)
11132               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11133                       yy_text, (int) moveType);
11134             return LoadGameOneMove(EndOfFile); /* tail recursion */
11135         }
11136         /* else fall thru */
11137
11138       case XBoardGame:
11139       case GNUChessGame:
11140       case PGNTag:
11141         /* Reached start of next game in file */
11142         if (appData.debugMode)
11143           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11144         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11145           case MT_NONE:
11146           case MT_CHECK:
11147             break;
11148           case MT_CHECKMATE:
11149           case MT_STAINMATE:
11150             if (WhiteOnMove(currentMove)) {
11151                 GameEnds(BlackWins, "Black mates", GE_FILE);
11152             } else {
11153                 GameEnds(WhiteWins, "White mates", GE_FILE);
11154             }
11155             break;
11156           case MT_STALEMATE:
11157             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11158             break;
11159         }
11160         done = TRUE;
11161         break;
11162
11163       case PositionDiagram:     /* should not happen; ignore */
11164       case ElapsedTime:         /* ignore */
11165       case NAG:                 /* ignore */
11166         if (appData.debugMode)
11167           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11168                   yy_text, (int) moveType);
11169         return LoadGameOneMove(EndOfFile); /* tail recursion */
11170
11171       case IllegalMove:
11172         if (appData.testLegality) {
11173             if (appData.debugMode)
11174               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11175             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11176                     (forwardMostMove / 2) + 1,
11177                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11178             DisplayError(move, 0);
11179             done = TRUE;
11180         } else {
11181             if (appData.debugMode)
11182               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11183                       yy_text, currentMoveString);
11184             fromX = currentMoveString[0] - AAA;
11185             fromY = currentMoveString[1] - ONE;
11186             toX = currentMoveString[2] - AAA;
11187             toY = currentMoveString[3] - ONE;
11188             promoChar = currentMoveString[4];
11189         }
11190         break;
11191
11192       case AmbiguousMove:
11193         if (appData.debugMode)
11194           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11195         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11196                 (forwardMostMove / 2) + 1,
11197                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11198         DisplayError(move, 0);
11199         done = TRUE;
11200         break;
11201
11202       default:
11203       case ImpossibleMove:
11204         if (appData.debugMode)
11205           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11206         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11207                 (forwardMostMove / 2) + 1,
11208                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11209         DisplayError(move, 0);
11210         done = TRUE;
11211         break;
11212     }
11213
11214     if (done) {
11215         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11216             DrawPosition(FALSE, boards[currentMove]);
11217             DisplayBothClocks();
11218             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11219               DisplayComment(currentMove - 1, commentList[currentMove]);
11220         }
11221         (void) StopLoadGameTimer();
11222         gameFileFP = NULL;
11223         cmailOldMove = forwardMostMove;
11224         return FALSE;
11225     } else {
11226         /* currentMoveString is set as a side-effect of yylex */
11227
11228         thinkOutput[0] = NULLCHAR;
11229         MakeMove(fromX, fromY, toX, toY, promoChar);
11230         currentMove = forwardMostMove;
11231         return TRUE;
11232     }
11233 }
11234
11235 /* Load the nth game from the given file */
11236 int
11237 LoadGameFromFile (char *filename, int n, char *title, int useList)
11238 {
11239     FILE *f;
11240     char buf[MSG_SIZ];
11241
11242     if (strcmp(filename, "-") == 0) {
11243         f = stdin;
11244         title = "stdin";
11245     } else {
11246         f = fopen(filename, "rb");
11247         if (f == NULL) {
11248           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11249             DisplayError(buf, errno);
11250             return FALSE;
11251         }
11252     }
11253     if (fseek(f, 0, 0) == -1) {
11254         /* f is not seekable; probably a pipe */
11255         useList = FALSE;
11256     }
11257     if (useList && n == 0) {
11258         int error = GameListBuild(f);
11259         if (error) {
11260             DisplayError(_("Cannot build game list"), error);
11261         } else if (!ListEmpty(&gameList) &&
11262                    ((ListGame *) gameList.tailPred)->number > 1) {
11263             GameListPopUp(f, title);
11264             return TRUE;
11265         }
11266         GameListDestroy();
11267         n = 1;
11268     }
11269     if (n == 0) n = 1;
11270     return LoadGame(f, n, title, FALSE);
11271 }
11272
11273
11274 void
11275 MakeRegisteredMove ()
11276 {
11277     int fromX, fromY, toX, toY;
11278     char promoChar;
11279     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11280         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11281           case CMAIL_MOVE:
11282           case CMAIL_DRAW:
11283             if (appData.debugMode)
11284               fprintf(debugFP, "Restoring %s for game %d\n",
11285                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11286
11287             thinkOutput[0] = NULLCHAR;
11288             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11289             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11290             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11291             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11292             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11293             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11294             MakeMove(fromX, fromY, toX, toY, promoChar);
11295             ShowMove(fromX, fromY, toX, toY);
11296
11297             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11298               case MT_NONE:
11299               case MT_CHECK:
11300                 break;
11301
11302               case MT_CHECKMATE:
11303               case MT_STAINMATE:
11304                 if (WhiteOnMove(currentMove)) {
11305                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11306                 } else {
11307                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11308                 }
11309                 break;
11310
11311               case MT_STALEMATE:
11312                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11313                 break;
11314             }
11315
11316             break;
11317
11318           case CMAIL_RESIGN:
11319             if (WhiteOnMove(currentMove)) {
11320                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11321             } else {
11322                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11323             }
11324             break;
11325
11326           case CMAIL_ACCEPT:
11327             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11328             break;
11329
11330           default:
11331             break;
11332         }
11333     }
11334
11335     return;
11336 }
11337
11338 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11339 int
11340 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11341 {
11342     int retVal;
11343
11344     if (gameNumber > nCmailGames) {
11345         DisplayError(_("No more games in this message"), 0);
11346         return FALSE;
11347     }
11348     if (f == lastLoadGameFP) {
11349         int offset = gameNumber - lastLoadGameNumber;
11350         if (offset == 0) {
11351             cmailMsg[0] = NULLCHAR;
11352             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11353                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11354                 nCmailMovesRegistered--;
11355             }
11356             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11357             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11358                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11359             }
11360         } else {
11361             if (! RegisterMove()) return FALSE;
11362         }
11363     }
11364
11365     retVal = LoadGame(f, gameNumber, title, useList);
11366
11367     /* Make move registered during previous look at this game, if any */
11368     MakeRegisteredMove();
11369
11370     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11371         commentList[currentMove]
11372           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11373         DisplayComment(currentMove - 1, commentList[currentMove]);
11374     }
11375
11376     return retVal;
11377 }
11378
11379 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11380 int
11381 ReloadGame (int offset)
11382 {
11383     int gameNumber = lastLoadGameNumber + offset;
11384     if (lastLoadGameFP == NULL) {
11385         DisplayError(_("No game has been loaded yet"), 0);
11386         return FALSE;
11387     }
11388     if (gameNumber <= 0) {
11389         DisplayError(_("Can't back up any further"), 0);
11390         return FALSE;
11391     }
11392     if (cmailMsgLoaded) {
11393         return CmailLoadGame(lastLoadGameFP, gameNumber,
11394                              lastLoadGameTitle, lastLoadGameUseList);
11395     } else {
11396         return LoadGame(lastLoadGameFP, gameNumber,
11397                         lastLoadGameTitle, lastLoadGameUseList);
11398     }
11399 }
11400
11401 int keys[EmptySquare+1];
11402
11403 int
11404 PositionMatches (Board b1, Board b2)
11405 {
11406     int r, f, sum=0;
11407     switch(appData.searchMode) {
11408         case 1: return CompareWithRights(b1, b2);
11409         case 2:
11410             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11411                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11412             }
11413             return TRUE;
11414         case 3:
11415             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11416               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11417                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11418             }
11419             return sum==0;
11420         case 4:
11421             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11422                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11423             }
11424             return sum==0;
11425     }
11426     return TRUE;
11427 }
11428
11429 #define Q_PROMO  4
11430 #define Q_EP     3
11431 #define Q_BCASTL 2
11432 #define Q_WCASTL 1
11433
11434 int pieceList[256], quickBoard[256];
11435 ChessSquare pieceType[256] = { EmptySquare };
11436 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11437 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11438 int soughtTotal, turn;
11439 Boolean epOK, flipSearch;
11440
11441 typedef struct {
11442     unsigned char piece, to;
11443 } Move;
11444
11445 #define DSIZE (250000)
11446
11447 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11448 Move *moveDatabase = initialSpace;
11449 unsigned int movePtr, dataSize = DSIZE;
11450
11451 int
11452 MakePieceList (Board board, int *counts)
11453 {
11454     int r, f, n=Q_PROMO, total=0;
11455     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11456     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11457         int sq = f + (r<<4);
11458         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11459             quickBoard[sq] = ++n;
11460             pieceList[n] = sq;
11461             pieceType[n] = board[r][f];
11462             counts[board[r][f]]++;
11463             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11464             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11465             total++;
11466         }
11467     }
11468     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11469     return total;
11470 }
11471
11472 void
11473 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11474 {
11475     int sq = fromX + (fromY<<4);
11476     int piece = quickBoard[sq];
11477     quickBoard[sq] = 0;
11478     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11479     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11480         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11481         moveDatabase[movePtr++].piece = Q_WCASTL;
11482         quickBoard[sq] = piece;
11483         piece = quickBoard[from]; quickBoard[from] = 0;
11484         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11485     } else
11486     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11487         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11488         moveDatabase[movePtr++].piece = Q_BCASTL;
11489         quickBoard[sq] = piece;
11490         piece = quickBoard[from]; quickBoard[from] = 0;
11491         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11492     } else
11493     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11494         quickBoard[(fromY<<4)+toX] = 0;
11495         moveDatabase[movePtr].piece = Q_EP;
11496         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11497         moveDatabase[movePtr].to = sq;
11498     } else
11499     if(promoPiece != pieceType[piece]) {
11500         moveDatabase[movePtr++].piece = Q_PROMO;
11501         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11502     }
11503     moveDatabase[movePtr].piece = piece;
11504     quickBoard[sq] = piece;
11505     movePtr++;
11506 }
11507
11508 int
11509 PackGame (Board board)
11510 {
11511     Move *newSpace = NULL;
11512     moveDatabase[movePtr].piece = 0; // terminate previous game
11513     if(movePtr > dataSize) {
11514         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11515         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11516         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11517         if(newSpace) {
11518             int i;
11519             Move *p = moveDatabase, *q = newSpace;
11520             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11521             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11522             moveDatabase = newSpace;
11523         } else { // calloc failed, we must be out of memory. Too bad...
11524             dataSize = 0; // prevent calloc events for all subsequent games
11525             return 0;     // and signal this one isn't cached
11526         }
11527     }
11528     movePtr++;
11529     MakePieceList(board, counts);
11530     return movePtr;
11531 }
11532
11533 int
11534 QuickCompare (Board board, int *minCounts, int *maxCounts)
11535 {   // compare according to search mode
11536     int r, f;
11537     switch(appData.searchMode)
11538     {
11539       case 1: // exact position match
11540         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11541         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11542             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11543         }
11544         break;
11545       case 2: // can have extra material on empty squares
11546         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11547             if(board[r][f] == EmptySquare) continue;
11548             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11549         }
11550         break;
11551       case 3: // material with exact Pawn structure
11552         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11553             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11554             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11555         } // fall through to material comparison
11556       case 4: // exact material
11557         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11558         break;
11559       case 6: // material range with given imbalance
11560         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11561         // fall through to range comparison
11562       case 5: // material range
11563         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11564     }
11565     return TRUE;
11566 }
11567
11568 int
11569 QuickScan (Board board, Move *move)
11570 {   // reconstruct game,and compare all positions in it
11571     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11572     do {
11573         int piece = move->piece;
11574         int to = move->to, from = pieceList[piece];
11575         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11576           if(!piece) return -1;
11577           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11578             piece = (++move)->piece;
11579             from = pieceList[piece];
11580             counts[pieceType[piece]]--;
11581             pieceType[piece] = (ChessSquare) move->to;
11582             counts[move->to]++;
11583           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11584             counts[pieceType[quickBoard[to]]]--;
11585             quickBoard[to] = 0; total--;
11586             move++;
11587             continue;
11588           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11589             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11590             from  = pieceList[piece]; // so this must be King
11591             quickBoard[from] = 0;
11592             pieceList[piece] = to;
11593             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11594             quickBoard[from] = 0; // rook
11595             quickBoard[to] = piece;
11596             to = move->to; piece = move->piece;
11597             goto aftercastle;
11598           }
11599         }
11600         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11601         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11602         quickBoard[from] = 0;
11603       aftercastle:
11604         quickBoard[to] = piece;
11605         pieceList[piece] = to;
11606         cnt++; turn ^= 3;
11607         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11608            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11609            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11610                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11611           ) {
11612             static int lastCounts[EmptySquare+1];
11613             int i;
11614             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11615             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11616         } else stretch = 0;
11617         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11618         move++;
11619     } while(1);
11620 }
11621
11622 void
11623 InitSearch ()
11624 {
11625     int r, f;
11626     flipSearch = FALSE;
11627     CopyBoard(soughtBoard, boards[currentMove]);
11628     soughtTotal = MakePieceList(soughtBoard, maxSought);
11629     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11630     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11631     CopyBoard(reverseBoard, boards[currentMove]);
11632     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11633         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11634         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11635         reverseBoard[r][f] = piece;
11636     }
11637     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11638     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11639     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11640                  || (boards[currentMove][CASTLING][2] == NoRights || 
11641                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11642                  && (boards[currentMove][CASTLING][5] == NoRights || 
11643                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11644       ) {
11645         flipSearch = TRUE;
11646         CopyBoard(flipBoard, soughtBoard);
11647         CopyBoard(rotateBoard, reverseBoard);
11648         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11649             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11650             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11651         }
11652     }
11653     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11654     if(appData.searchMode >= 5) {
11655         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11656         MakePieceList(soughtBoard, minSought);
11657         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11658     }
11659     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11660         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11661 }
11662
11663 GameInfo dummyInfo;
11664
11665 int
11666 GameContainsPosition (FILE *f, ListGame *lg)
11667 {
11668     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11669     int fromX, fromY, toX, toY;
11670     char promoChar;
11671     static int initDone=FALSE;
11672
11673     // weed out games based on numerical tag comparison
11674     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11675     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11676     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11677     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11678     if(!initDone) {
11679         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11680         initDone = TRUE;
11681     }
11682     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11683     else CopyBoard(boards[scratch], initialPosition); // default start position
11684     if(lg->moves) {
11685         turn = btm + 1;
11686         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11687         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11688     }
11689     if(btm) plyNr++;
11690     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11691     fseek(f, lg->offset, 0);
11692     yynewfile(f);
11693     while(1) {
11694         yyboardindex = scratch;
11695         quickFlag = plyNr+1;
11696         next = Myylex();
11697         quickFlag = 0;
11698         switch(next) {
11699             case PGNTag:
11700                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11701             default:
11702                 continue;
11703
11704             case XBoardGame:
11705             case GNUChessGame:
11706                 if(plyNr) return -1; // after we have seen moves, this is for new game
11707               continue;
11708
11709             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11710             case ImpossibleMove:
11711             case WhiteWins: // game ends here with these four
11712             case BlackWins:
11713             case GameIsDrawn:
11714             case GameUnfinished:
11715                 return -1;
11716
11717             case IllegalMove:
11718                 if(appData.testLegality) return -1;
11719             case WhiteCapturesEnPassant:
11720             case BlackCapturesEnPassant:
11721             case WhitePromotion:
11722             case BlackPromotion:
11723             case WhiteNonPromotion:
11724             case BlackNonPromotion:
11725             case NormalMove:
11726             case WhiteKingSideCastle:
11727             case WhiteQueenSideCastle:
11728             case BlackKingSideCastle:
11729             case BlackQueenSideCastle:
11730             case WhiteKingSideCastleWild:
11731             case WhiteQueenSideCastleWild:
11732             case BlackKingSideCastleWild:
11733             case BlackQueenSideCastleWild:
11734             case WhiteHSideCastleFR:
11735             case WhiteASideCastleFR:
11736             case BlackHSideCastleFR:
11737             case BlackASideCastleFR:
11738                 fromX = currentMoveString[0] - AAA;
11739                 fromY = currentMoveString[1] - ONE;
11740                 toX = currentMoveString[2] - AAA;
11741                 toY = currentMoveString[3] - ONE;
11742                 promoChar = currentMoveString[4];
11743                 break;
11744             case WhiteDrop:
11745             case BlackDrop:
11746                 fromX = next == WhiteDrop ?
11747                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11748                   (int) CharToPiece(ToLower(currentMoveString[0]));
11749                 fromY = DROP_RANK;
11750                 toX = currentMoveString[2] - AAA;
11751                 toY = currentMoveString[3] - ONE;
11752                 promoChar = 0;
11753                 break;
11754         }
11755         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11756         plyNr++;
11757         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11758         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11759         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11760         if(appData.findMirror) {
11761             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11762             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11763         }
11764     }
11765 }
11766
11767 /* Load the nth game from open file f */
11768 int
11769 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11770 {
11771     ChessMove cm;
11772     char buf[MSG_SIZ];
11773     int gn = gameNumber;
11774     ListGame *lg = NULL;
11775     int numPGNTags = 0;
11776     int err, pos = -1;
11777     GameMode oldGameMode;
11778     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11779
11780     if (appData.debugMode)
11781         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11782
11783     if (gameMode == Training )
11784         SetTrainingModeOff();
11785
11786     oldGameMode = gameMode;
11787     if (gameMode != BeginningOfGame) {
11788       Reset(FALSE, TRUE);
11789     }
11790
11791     gameFileFP = f;
11792     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11793         fclose(lastLoadGameFP);
11794     }
11795
11796     if (useList) {
11797         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11798
11799         if (lg) {
11800             fseek(f, lg->offset, 0);
11801             GameListHighlight(gameNumber);
11802             pos = lg->position;
11803             gn = 1;
11804         }
11805         else {
11806             DisplayError(_("Game number out of range"), 0);
11807             return FALSE;
11808         }
11809     } else {
11810         GameListDestroy();
11811         if (fseek(f, 0, 0) == -1) {
11812             if (f == lastLoadGameFP ?
11813                 gameNumber == lastLoadGameNumber + 1 :
11814                 gameNumber == 1) {
11815                 gn = 1;
11816             } else {
11817                 DisplayError(_("Can't seek on game file"), 0);
11818                 return FALSE;
11819             }
11820         }
11821     }
11822     lastLoadGameFP = f;
11823     lastLoadGameNumber = gameNumber;
11824     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11825     lastLoadGameUseList = useList;
11826
11827     yynewfile(f);
11828
11829     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11830       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11831                 lg->gameInfo.black);
11832             DisplayTitle(buf);
11833     } else if (*title != NULLCHAR) {
11834         if (gameNumber > 1) {
11835           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11836             DisplayTitle(buf);
11837         } else {
11838             DisplayTitle(title);
11839         }
11840     }
11841
11842     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11843         gameMode = PlayFromGameFile;
11844         ModeHighlight();
11845     }
11846
11847     currentMove = forwardMostMove = backwardMostMove = 0;
11848     CopyBoard(boards[0], initialPosition);
11849     StopClocks();
11850
11851     /*
11852      * Skip the first gn-1 games in the file.
11853      * Also skip over anything that precedes an identifiable
11854      * start of game marker, to avoid being confused by
11855      * garbage at the start of the file.  Currently
11856      * recognized start of game markers are the move number "1",
11857      * the pattern "gnuchess .* game", the pattern
11858      * "^[#;%] [^ ]* game file", and a PGN tag block.
11859      * A game that starts with one of the latter two patterns
11860      * will also have a move number 1, possibly
11861      * following a position diagram.
11862      * 5-4-02: Let's try being more lenient and allowing a game to
11863      * start with an unnumbered move.  Does that break anything?
11864      */
11865     cm = lastLoadGameStart = EndOfFile;
11866     while (gn > 0) {
11867         yyboardindex = forwardMostMove;
11868         cm = (ChessMove) Myylex();
11869         switch (cm) {
11870           case EndOfFile:
11871             if (cmailMsgLoaded) {
11872                 nCmailGames = CMAIL_MAX_GAMES - gn;
11873             } else {
11874                 Reset(TRUE, TRUE);
11875                 DisplayError(_("Game not found in file"), 0);
11876             }
11877             return FALSE;
11878
11879           case GNUChessGame:
11880           case XBoardGame:
11881             gn--;
11882             lastLoadGameStart = cm;
11883             break;
11884
11885           case MoveNumberOne:
11886             switch (lastLoadGameStart) {
11887               case GNUChessGame:
11888               case XBoardGame:
11889               case PGNTag:
11890                 break;
11891               case MoveNumberOne:
11892               case EndOfFile:
11893                 gn--;           /* count this game */
11894                 lastLoadGameStart = cm;
11895                 break;
11896               default:
11897                 /* impossible */
11898                 break;
11899             }
11900             break;
11901
11902           case PGNTag:
11903             switch (lastLoadGameStart) {
11904               case GNUChessGame:
11905               case PGNTag:
11906               case MoveNumberOne:
11907               case EndOfFile:
11908                 gn--;           /* count this game */
11909                 lastLoadGameStart = cm;
11910                 break;
11911               case XBoardGame:
11912                 lastLoadGameStart = cm; /* game counted already */
11913                 break;
11914               default:
11915                 /* impossible */
11916                 break;
11917             }
11918             if (gn > 0) {
11919                 do {
11920                     yyboardindex = forwardMostMove;
11921                     cm = (ChessMove) Myylex();
11922                 } while (cm == PGNTag || cm == Comment);
11923             }
11924             break;
11925
11926           case WhiteWins:
11927           case BlackWins:
11928           case GameIsDrawn:
11929             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11930                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11931                     != CMAIL_OLD_RESULT) {
11932                     nCmailResults ++ ;
11933                     cmailResult[  CMAIL_MAX_GAMES
11934                                 - gn - 1] = CMAIL_OLD_RESULT;
11935                 }
11936             }
11937             break;
11938
11939           case NormalMove:
11940             /* Only a NormalMove can be at the start of a game
11941              * without a position diagram. */
11942             if (lastLoadGameStart == EndOfFile ) {
11943               gn--;
11944               lastLoadGameStart = MoveNumberOne;
11945             }
11946             break;
11947
11948           default:
11949             break;
11950         }
11951     }
11952
11953     if (appData.debugMode)
11954       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11955
11956     if (cm == XBoardGame) {
11957         /* Skip any header junk before position diagram and/or move 1 */
11958         for (;;) {
11959             yyboardindex = forwardMostMove;
11960             cm = (ChessMove) Myylex();
11961
11962             if (cm == EndOfFile ||
11963                 cm == GNUChessGame || cm == XBoardGame) {
11964                 /* Empty game; pretend end-of-file and handle later */
11965                 cm = EndOfFile;
11966                 break;
11967             }
11968
11969             if (cm == MoveNumberOne || cm == PositionDiagram ||
11970                 cm == PGNTag || cm == Comment)
11971               break;
11972         }
11973     } else if (cm == GNUChessGame) {
11974         if (gameInfo.event != NULL) {
11975             free(gameInfo.event);
11976         }
11977         gameInfo.event = StrSave(yy_text);
11978     }
11979
11980     startedFromSetupPosition = FALSE;
11981     while (cm == PGNTag) {
11982         if (appData.debugMode)
11983           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11984         err = ParsePGNTag(yy_text, &gameInfo);
11985         if (!err) numPGNTags++;
11986
11987         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11988         if(gameInfo.variant != oldVariant) {
11989             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11990             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11991             InitPosition(TRUE);
11992             oldVariant = gameInfo.variant;
11993             if (appData.debugMode)
11994               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11995         }
11996
11997
11998         if (gameInfo.fen != NULL) {
11999           Board initial_position;
12000           startedFromSetupPosition = TRUE;
12001           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12002             Reset(TRUE, TRUE);
12003             DisplayError(_("Bad FEN position in file"), 0);
12004             return FALSE;
12005           }
12006           CopyBoard(boards[0], initial_position);
12007           if (blackPlaysFirst) {
12008             currentMove = forwardMostMove = backwardMostMove = 1;
12009             CopyBoard(boards[1], initial_position);
12010             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12011             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12012             timeRemaining[0][1] = whiteTimeRemaining;
12013             timeRemaining[1][1] = blackTimeRemaining;
12014             if (commentList[0] != NULL) {
12015               commentList[1] = commentList[0];
12016               commentList[0] = NULL;
12017             }
12018           } else {
12019             currentMove = forwardMostMove = backwardMostMove = 0;
12020           }
12021           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12022           {   int i;
12023               initialRulePlies = FENrulePlies;
12024               for( i=0; i< nrCastlingRights; i++ )
12025                   initialRights[i] = initial_position[CASTLING][i];
12026           }
12027           yyboardindex = forwardMostMove;
12028           free(gameInfo.fen);
12029           gameInfo.fen = NULL;
12030         }
12031
12032         yyboardindex = forwardMostMove;
12033         cm = (ChessMove) Myylex();
12034
12035         /* Handle comments interspersed among the tags */
12036         while (cm == Comment) {
12037             char *p;
12038             if (appData.debugMode)
12039               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12040             p = yy_text;
12041             AppendComment(currentMove, p, FALSE);
12042             yyboardindex = forwardMostMove;
12043             cm = (ChessMove) Myylex();
12044         }
12045     }
12046
12047     /* don't rely on existence of Event tag since if game was
12048      * pasted from clipboard the Event tag may not exist
12049      */
12050     if (numPGNTags > 0){
12051         char *tags;
12052         if (gameInfo.variant == VariantNormal) {
12053           VariantClass v = StringToVariant(gameInfo.event);
12054           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12055           if(v < VariantShogi) gameInfo.variant = v;
12056         }
12057         if (!matchMode) {
12058           if( appData.autoDisplayTags ) {
12059             tags = PGNTags(&gameInfo);
12060             TagsPopUp(tags, CmailMsg());
12061             free(tags);
12062           }
12063         }
12064     } else {
12065         /* Make something up, but don't display it now */
12066         SetGameInfo();
12067         TagsPopDown();
12068     }
12069
12070     if (cm == PositionDiagram) {
12071         int i, j;
12072         char *p;
12073         Board initial_position;
12074
12075         if (appData.debugMode)
12076           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12077
12078         if (!startedFromSetupPosition) {
12079             p = yy_text;
12080             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12081               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12082                 switch (*p) {
12083                   case '{':
12084                   case '[':
12085                   case '-':
12086                   case ' ':
12087                   case '\t':
12088                   case '\n':
12089                   case '\r':
12090                     break;
12091                   default:
12092                     initial_position[i][j++] = CharToPiece(*p);
12093                     break;
12094                 }
12095             while (*p == ' ' || *p == '\t' ||
12096                    *p == '\n' || *p == '\r') p++;
12097
12098             if (strncmp(p, "black", strlen("black"))==0)
12099               blackPlaysFirst = TRUE;
12100             else
12101               blackPlaysFirst = FALSE;
12102             startedFromSetupPosition = TRUE;
12103
12104             CopyBoard(boards[0], initial_position);
12105             if (blackPlaysFirst) {
12106                 currentMove = forwardMostMove = backwardMostMove = 1;
12107                 CopyBoard(boards[1], initial_position);
12108                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12109                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12110                 timeRemaining[0][1] = whiteTimeRemaining;
12111                 timeRemaining[1][1] = blackTimeRemaining;
12112                 if (commentList[0] != NULL) {
12113                     commentList[1] = commentList[0];
12114                     commentList[0] = NULL;
12115                 }
12116             } else {
12117                 currentMove = forwardMostMove = backwardMostMove = 0;
12118             }
12119         }
12120         yyboardindex = forwardMostMove;
12121         cm = (ChessMove) Myylex();
12122     }
12123
12124     if (first.pr == NoProc) {
12125         StartChessProgram(&first);
12126     }
12127     InitChessProgram(&first, FALSE);
12128     SendToProgram("force\n", &first);
12129     if (startedFromSetupPosition) {
12130         SendBoard(&first, forwardMostMove);
12131     if (appData.debugMode) {
12132         fprintf(debugFP, "Load Game\n");
12133     }
12134         DisplayBothClocks();
12135     }
12136
12137     /* [HGM] server: flag to write setup moves in broadcast file as one */
12138     loadFlag = appData.suppressLoadMoves;
12139
12140     while (cm == Comment) {
12141         char *p;
12142         if (appData.debugMode)
12143           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12144         p = yy_text;
12145         AppendComment(currentMove, p, FALSE);
12146         yyboardindex = forwardMostMove;
12147         cm = (ChessMove) Myylex();
12148     }
12149
12150     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12151         cm == WhiteWins || cm == BlackWins ||
12152         cm == GameIsDrawn || cm == GameUnfinished) {
12153         DisplayMessage("", _("No moves in game"));
12154         if (cmailMsgLoaded) {
12155             if (appData.debugMode)
12156               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12157             ClearHighlights();
12158             flipView = FALSE;
12159         }
12160         DrawPosition(FALSE, boards[currentMove]);
12161         DisplayBothClocks();
12162         gameMode = EditGame;
12163         ModeHighlight();
12164         gameFileFP = NULL;
12165         cmailOldMove = 0;
12166         return TRUE;
12167     }
12168
12169     // [HGM] PV info: routine tests if comment empty
12170     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12171         DisplayComment(currentMove - 1, commentList[currentMove]);
12172     }
12173     if (!matchMode && appData.timeDelay != 0)
12174       DrawPosition(FALSE, boards[currentMove]);
12175
12176     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12177       programStats.ok_to_send = 1;
12178     }
12179
12180     /* if the first token after the PGN tags is a move
12181      * and not move number 1, retrieve it from the parser
12182      */
12183     if (cm != MoveNumberOne)
12184         LoadGameOneMove(cm);
12185
12186     /* load the remaining moves from the file */
12187     while (LoadGameOneMove(EndOfFile)) {
12188       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12189       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12190     }
12191
12192     /* rewind to the start of the game */
12193     currentMove = backwardMostMove;
12194
12195     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12196
12197     if (oldGameMode == AnalyzeFile ||
12198         oldGameMode == AnalyzeMode) {
12199       AnalyzeFileEvent();
12200     }
12201
12202     if (!matchMode && pos > 0) {
12203         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12204     } else
12205     if (matchMode || appData.timeDelay == 0) {
12206       ToEndEvent();
12207     } else if (appData.timeDelay > 0) {
12208       AutoPlayGameLoop();
12209     }
12210
12211     if (appData.debugMode)
12212         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12213
12214     loadFlag = 0; /* [HGM] true game starts */
12215     return TRUE;
12216 }
12217
12218 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12219 int
12220 ReloadPosition (int offset)
12221 {
12222     int positionNumber = lastLoadPositionNumber + offset;
12223     if (lastLoadPositionFP == NULL) {
12224         DisplayError(_("No position has been loaded yet"), 0);
12225         return FALSE;
12226     }
12227     if (positionNumber <= 0) {
12228         DisplayError(_("Can't back up any further"), 0);
12229         return FALSE;
12230     }
12231     return LoadPosition(lastLoadPositionFP, positionNumber,
12232                         lastLoadPositionTitle);
12233 }
12234
12235 /* Load the nth position from the given file */
12236 int
12237 LoadPositionFromFile (char *filename, int n, char *title)
12238 {
12239     FILE *f;
12240     char buf[MSG_SIZ];
12241
12242     if (strcmp(filename, "-") == 0) {
12243         return LoadPosition(stdin, n, "stdin");
12244     } else {
12245         f = fopen(filename, "rb");
12246         if (f == NULL) {
12247             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12248             DisplayError(buf, errno);
12249             return FALSE;
12250         } else {
12251             return LoadPosition(f, n, title);
12252         }
12253     }
12254 }
12255
12256 /* Load the nth position from the given open file, and close it */
12257 int
12258 LoadPosition (FILE *f, int positionNumber, char *title)
12259 {
12260     char *p, line[MSG_SIZ];
12261     Board initial_position;
12262     int i, j, fenMode, pn;
12263
12264     if (gameMode == Training )
12265         SetTrainingModeOff();
12266
12267     if (gameMode != BeginningOfGame) {
12268         Reset(FALSE, TRUE);
12269     }
12270     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12271         fclose(lastLoadPositionFP);
12272     }
12273     if (positionNumber == 0) positionNumber = 1;
12274     lastLoadPositionFP = f;
12275     lastLoadPositionNumber = positionNumber;
12276     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12277     if (first.pr == NoProc && !appData.noChessProgram) {
12278       StartChessProgram(&first);
12279       InitChessProgram(&first, FALSE);
12280     }
12281     pn = positionNumber;
12282     if (positionNumber < 0) {
12283         /* Negative position number means to seek to that byte offset */
12284         if (fseek(f, -positionNumber, 0) == -1) {
12285             DisplayError(_("Can't seek on position file"), 0);
12286             return FALSE;
12287         };
12288         pn = 1;
12289     } else {
12290         if (fseek(f, 0, 0) == -1) {
12291             if (f == lastLoadPositionFP ?
12292                 positionNumber == lastLoadPositionNumber + 1 :
12293                 positionNumber == 1) {
12294                 pn = 1;
12295             } else {
12296                 DisplayError(_("Can't seek on position file"), 0);
12297                 return FALSE;
12298             }
12299         }
12300     }
12301     /* See if this file is FEN or old-style xboard */
12302     if (fgets(line, MSG_SIZ, f) == NULL) {
12303         DisplayError(_("Position not found in file"), 0);
12304         return FALSE;
12305     }
12306     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12307     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12308
12309     if (pn >= 2) {
12310         if (fenMode || line[0] == '#') pn--;
12311         while (pn > 0) {
12312             /* skip positions before number pn */
12313             if (fgets(line, MSG_SIZ, f) == NULL) {
12314                 Reset(TRUE, TRUE);
12315                 DisplayError(_("Position not found in file"), 0);
12316                 return FALSE;
12317             }
12318             if (fenMode || line[0] == '#') pn--;
12319         }
12320     }
12321
12322     if (fenMode) {
12323         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12324             DisplayError(_("Bad FEN position in file"), 0);
12325             return FALSE;
12326         }
12327     } else {
12328         (void) fgets(line, MSG_SIZ, f);
12329         (void) fgets(line, MSG_SIZ, f);
12330
12331         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12332             (void) fgets(line, MSG_SIZ, f);
12333             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12334                 if (*p == ' ')
12335                   continue;
12336                 initial_position[i][j++] = CharToPiece(*p);
12337             }
12338         }
12339
12340         blackPlaysFirst = FALSE;
12341         if (!feof(f)) {
12342             (void) fgets(line, MSG_SIZ, f);
12343             if (strncmp(line, "black", strlen("black"))==0)
12344               blackPlaysFirst = TRUE;
12345         }
12346     }
12347     startedFromSetupPosition = TRUE;
12348
12349     CopyBoard(boards[0], initial_position);
12350     if (blackPlaysFirst) {
12351         currentMove = forwardMostMove = backwardMostMove = 1;
12352         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12353         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12354         CopyBoard(boards[1], initial_position);
12355         DisplayMessage("", _("Black to play"));
12356     } else {
12357         currentMove = forwardMostMove = backwardMostMove = 0;
12358         DisplayMessage("", _("White to play"));
12359     }
12360     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12361     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12362         SendToProgram("force\n", &first);
12363         SendBoard(&first, forwardMostMove);
12364     }
12365     if (appData.debugMode) {
12366 int i, j;
12367   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12368   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12369         fprintf(debugFP, "Load Position\n");
12370     }
12371
12372     if (positionNumber > 1) {
12373       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12374         DisplayTitle(line);
12375     } else {
12376         DisplayTitle(title);
12377     }
12378     gameMode = EditGame;
12379     ModeHighlight();
12380     ResetClocks();
12381     timeRemaining[0][1] = whiteTimeRemaining;
12382     timeRemaining[1][1] = blackTimeRemaining;
12383     DrawPosition(FALSE, boards[currentMove]);
12384
12385     return TRUE;
12386 }
12387
12388
12389 void
12390 CopyPlayerNameIntoFileName (char **dest, char *src)
12391 {
12392     while (*src != NULLCHAR && *src != ',') {
12393         if (*src == ' ') {
12394             *(*dest)++ = '_';
12395             src++;
12396         } else {
12397             *(*dest)++ = *src++;
12398         }
12399     }
12400 }
12401
12402 char *
12403 DefaultFileName (char *ext)
12404 {
12405     static char def[MSG_SIZ];
12406     char *p;
12407
12408     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12409         p = def;
12410         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12411         *p++ = '-';
12412         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12413         *p++ = '.';
12414         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12415     } else {
12416         def[0] = NULLCHAR;
12417     }
12418     return def;
12419 }
12420
12421 /* Save the current game to the given file */
12422 int
12423 SaveGameToFile (char *filename, int append)
12424 {
12425     FILE *f;
12426     char buf[MSG_SIZ];
12427     int result, i, t,tot=0;
12428
12429     if (strcmp(filename, "-") == 0) {
12430         return SaveGame(stdout, 0, NULL);
12431     } else {
12432         for(i=0; i<10; i++) { // upto 10 tries
12433              f = fopen(filename, append ? "a" : "w");
12434              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12435              if(f || errno != 13) break;
12436              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12437              tot += t;
12438         }
12439         if (f == NULL) {
12440             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12441             DisplayError(buf, errno);
12442             return FALSE;
12443         } else {
12444             safeStrCpy(buf, lastMsg, MSG_SIZ);
12445             DisplayMessage(_("Waiting for access to save file"), "");
12446             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12447             DisplayMessage(_("Saving game"), "");
12448             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12449             result = SaveGame(f, 0, NULL);
12450             DisplayMessage(buf, "");
12451             return result;
12452         }
12453     }
12454 }
12455
12456 char *
12457 SavePart (char *str)
12458 {
12459     static char buf[MSG_SIZ];
12460     char *p;
12461
12462     p = strchr(str, ' ');
12463     if (p == NULL) return str;
12464     strncpy(buf, str, p - str);
12465     buf[p - str] = NULLCHAR;
12466     return buf;
12467 }
12468
12469 #define PGN_MAX_LINE 75
12470
12471 #define PGN_SIDE_WHITE  0
12472 #define PGN_SIDE_BLACK  1
12473
12474 static int
12475 FindFirstMoveOutOfBook (int side)
12476 {
12477     int result = -1;
12478
12479     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12480         int index = backwardMostMove;
12481         int has_book_hit = 0;
12482
12483         if( (index % 2) != side ) {
12484             index++;
12485         }
12486
12487         while( index < forwardMostMove ) {
12488             /* Check to see if engine is in book */
12489             int depth = pvInfoList[index].depth;
12490             int score = pvInfoList[index].score;
12491             int in_book = 0;
12492
12493             if( depth <= 2 ) {
12494                 in_book = 1;
12495             }
12496             else if( score == 0 && depth == 63 ) {
12497                 in_book = 1; /* Zappa */
12498             }
12499             else if( score == 2 && depth == 99 ) {
12500                 in_book = 1; /* Abrok */
12501             }
12502
12503             has_book_hit += in_book;
12504
12505             if( ! in_book ) {
12506                 result = index;
12507
12508                 break;
12509             }
12510
12511             index += 2;
12512         }
12513     }
12514
12515     return result;
12516 }
12517
12518 void
12519 GetOutOfBookInfo (char * buf)
12520 {
12521     int oob[2];
12522     int i;
12523     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12524
12525     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12526     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12527
12528     *buf = '\0';
12529
12530     if( oob[0] >= 0 || oob[1] >= 0 ) {
12531         for( i=0; i<2; i++ ) {
12532             int idx = oob[i];
12533
12534             if( idx >= 0 ) {
12535                 if( i > 0 && oob[0] >= 0 ) {
12536                     strcat( buf, "   " );
12537                 }
12538
12539                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12540                 sprintf( buf+strlen(buf), "%s%.2f",
12541                     pvInfoList[idx].score >= 0 ? "+" : "",
12542                     pvInfoList[idx].score / 100.0 );
12543             }
12544         }
12545     }
12546 }
12547
12548 /* Save game in PGN style and close the file */
12549 int
12550 SaveGamePGN (FILE *f)
12551 {
12552     int i, offset, linelen, newblock;
12553 //    char *movetext;
12554     char numtext[32];
12555     int movelen, numlen, blank;
12556     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12557
12558     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12559
12560     PrintPGNTags(f, &gameInfo);
12561
12562     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12563
12564     if (backwardMostMove > 0 || startedFromSetupPosition) {
12565         char *fen = PositionToFEN(backwardMostMove, NULL);
12566         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12567         fprintf(f, "\n{--------------\n");
12568         PrintPosition(f, backwardMostMove);
12569         fprintf(f, "--------------}\n");
12570         free(fen);
12571     }
12572     else {
12573         /* [AS] Out of book annotation */
12574         if( appData.saveOutOfBookInfo ) {
12575             char buf[64];
12576
12577             GetOutOfBookInfo( buf );
12578
12579             if( buf[0] != '\0' ) {
12580                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12581             }
12582         }
12583
12584         fprintf(f, "\n");
12585     }
12586
12587     i = backwardMostMove;
12588     linelen = 0;
12589     newblock = TRUE;
12590
12591     while (i < forwardMostMove) {
12592         /* Print comments preceding this move */
12593         if (commentList[i] != NULL) {
12594             if (linelen > 0) fprintf(f, "\n");
12595             fprintf(f, "%s", commentList[i]);
12596             linelen = 0;
12597             newblock = TRUE;
12598         }
12599
12600         /* Format move number */
12601         if ((i % 2) == 0)
12602           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12603         else
12604           if (newblock)
12605             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12606           else
12607             numtext[0] = NULLCHAR;
12608
12609         numlen = strlen(numtext);
12610         newblock = FALSE;
12611
12612         /* Print move number */
12613         blank = linelen > 0 && numlen > 0;
12614         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12615             fprintf(f, "\n");
12616             linelen = 0;
12617             blank = 0;
12618         }
12619         if (blank) {
12620             fprintf(f, " ");
12621             linelen++;
12622         }
12623         fprintf(f, "%s", numtext);
12624         linelen += numlen;
12625
12626         /* Get move */
12627         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12628         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12629
12630         /* Print move */
12631         blank = linelen > 0 && movelen > 0;
12632         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12633             fprintf(f, "\n");
12634             linelen = 0;
12635             blank = 0;
12636         }
12637         if (blank) {
12638             fprintf(f, " ");
12639             linelen++;
12640         }
12641         fprintf(f, "%s", move_buffer);
12642         linelen += movelen;
12643
12644         /* [AS] Add PV info if present */
12645         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12646             /* [HGM] add time */
12647             char buf[MSG_SIZ]; int seconds;
12648
12649             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12650
12651             if( seconds <= 0)
12652               buf[0] = 0;
12653             else
12654               if( seconds < 30 )
12655                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12656               else
12657                 {
12658                   seconds = (seconds + 4)/10; // round to full seconds
12659                   if( seconds < 60 )
12660                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12661                   else
12662                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12663                 }
12664
12665             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12666                       pvInfoList[i].score >= 0 ? "+" : "",
12667                       pvInfoList[i].score / 100.0,
12668                       pvInfoList[i].depth,
12669                       buf );
12670
12671             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12672
12673             /* Print score/depth */
12674             blank = linelen > 0 && movelen > 0;
12675             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12676                 fprintf(f, "\n");
12677                 linelen = 0;
12678                 blank = 0;
12679             }
12680             if (blank) {
12681                 fprintf(f, " ");
12682                 linelen++;
12683             }
12684             fprintf(f, "%s", move_buffer);
12685             linelen += movelen;
12686         }
12687
12688         i++;
12689     }
12690
12691     /* Start a new line */
12692     if (linelen > 0) fprintf(f, "\n");
12693
12694     /* Print comments after last move */
12695     if (commentList[i] != NULL) {
12696         fprintf(f, "%s\n", commentList[i]);
12697     }
12698
12699     /* Print result */
12700     if (gameInfo.resultDetails != NULL &&
12701         gameInfo.resultDetails[0] != NULLCHAR) {
12702         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12703                 PGNResult(gameInfo.result));
12704     } else {
12705         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12706     }
12707
12708     fclose(f);
12709     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12710     return TRUE;
12711 }
12712
12713 /* Save game in old style and close the file */
12714 int
12715 SaveGameOldStyle (FILE *f)
12716 {
12717     int i, offset;
12718     time_t tm;
12719
12720     tm = time((time_t *) NULL);
12721
12722     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12723     PrintOpponents(f);
12724
12725     if (backwardMostMove > 0 || startedFromSetupPosition) {
12726         fprintf(f, "\n[--------------\n");
12727         PrintPosition(f, backwardMostMove);
12728         fprintf(f, "--------------]\n");
12729     } else {
12730         fprintf(f, "\n");
12731     }
12732
12733     i = backwardMostMove;
12734     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12735
12736     while (i < forwardMostMove) {
12737         if (commentList[i] != NULL) {
12738             fprintf(f, "[%s]\n", commentList[i]);
12739         }
12740
12741         if ((i % 2) == 1) {
12742             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12743             i++;
12744         } else {
12745             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12746             i++;
12747             if (commentList[i] != NULL) {
12748                 fprintf(f, "\n");
12749                 continue;
12750             }
12751             if (i >= forwardMostMove) {
12752                 fprintf(f, "\n");
12753                 break;
12754             }
12755             fprintf(f, "%s\n", parseList[i]);
12756             i++;
12757         }
12758     }
12759
12760     if (commentList[i] != NULL) {
12761         fprintf(f, "[%s]\n", commentList[i]);
12762     }
12763
12764     /* This isn't really the old style, but it's close enough */
12765     if (gameInfo.resultDetails != NULL &&
12766         gameInfo.resultDetails[0] != NULLCHAR) {
12767         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12768                 gameInfo.resultDetails);
12769     } else {
12770         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12771     }
12772
12773     fclose(f);
12774     return TRUE;
12775 }
12776
12777 /* Save the current game to open file f and close the file */
12778 int
12779 SaveGame (FILE *f, int dummy, char *dummy2)
12780 {
12781     if (gameMode == EditPosition) EditPositionDone(TRUE);
12782     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12783     if (appData.oldSaveStyle)
12784       return SaveGameOldStyle(f);
12785     else
12786       return SaveGamePGN(f);
12787 }
12788
12789 /* Save the current position to the given file */
12790 int
12791 SavePositionToFile (char *filename)
12792 {
12793     FILE *f;
12794     char buf[MSG_SIZ];
12795
12796     if (strcmp(filename, "-") == 0) {
12797         return SavePosition(stdout, 0, NULL);
12798     } else {
12799         f = fopen(filename, "a");
12800         if (f == NULL) {
12801             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12802             DisplayError(buf, errno);
12803             return FALSE;
12804         } else {
12805             safeStrCpy(buf, lastMsg, MSG_SIZ);
12806             DisplayMessage(_("Waiting for access to save file"), "");
12807             flock(fileno(f), LOCK_EX); // [HGM] lock
12808             DisplayMessage(_("Saving position"), "");
12809             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12810             SavePosition(f, 0, NULL);
12811             DisplayMessage(buf, "");
12812             return TRUE;
12813         }
12814     }
12815 }
12816
12817 /* Save the current position to the given open file and close the file */
12818 int
12819 SavePosition (FILE *f, int dummy, char *dummy2)
12820 {
12821     time_t tm;
12822     char *fen;
12823
12824     if (gameMode == EditPosition) EditPositionDone(TRUE);
12825     if (appData.oldSaveStyle) {
12826         tm = time((time_t *) NULL);
12827
12828         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12829         PrintOpponents(f);
12830         fprintf(f, "[--------------\n");
12831         PrintPosition(f, currentMove);
12832         fprintf(f, "--------------]\n");
12833     } else {
12834         fen = PositionToFEN(currentMove, NULL);
12835         fprintf(f, "%s\n", fen);
12836         free(fen);
12837     }
12838     fclose(f);
12839     return TRUE;
12840 }
12841
12842 void
12843 ReloadCmailMsgEvent (int unregister)
12844 {
12845 #if !WIN32
12846     static char *inFilename = NULL;
12847     static char *outFilename;
12848     int i;
12849     struct stat inbuf, outbuf;
12850     int status;
12851
12852     /* Any registered moves are unregistered if unregister is set, */
12853     /* i.e. invoked by the signal handler */
12854     if (unregister) {
12855         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12856             cmailMoveRegistered[i] = FALSE;
12857             if (cmailCommentList[i] != NULL) {
12858                 free(cmailCommentList[i]);
12859                 cmailCommentList[i] = NULL;
12860             }
12861         }
12862         nCmailMovesRegistered = 0;
12863     }
12864
12865     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12866         cmailResult[i] = CMAIL_NOT_RESULT;
12867     }
12868     nCmailResults = 0;
12869
12870     if (inFilename == NULL) {
12871         /* Because the filenames are static they only get malloced once  */
12872         /* and they never get freed                                      */
12873         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12874         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12875
12876         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12877         sprintf(outFilename, "%s.out", appData.cmailGameName);
12878     }
12879
12880     status = stat(outFilename, &outbuf);
12881     if (status < 0) {
12882         cmailMailedMove = FALSE;
12883     } else {
12884         status = stat(inFilename, &inbuf);
12885         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12886     }
12887
12888     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12889        counts the games, notes how each one terminated, etc.
12890
12891        It would be nice to remove this kludge and instead gather all
12892        the information while building the game list.  (And to keep it
12893        in the game list nodes instead of having a bunch of fixed-size
12894        parallel arrays.)  Note this will require getting each game's
12895        termination from the PGN tags, as the game list builder does
12896        not process the game moves.  --mann
12897        */
12898     cmailMsgLoaded = TRUE;
12899     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12900
12901     /* Load first game in the file or popup game menu */
12902     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12903
12904 #endif /* !WIN32 */
12905     return;
12906 }
12907
12908 int
12909 RegisterMove ()
12910 {
12911     FILE *f;
12912     char string[MSG_SIZ];
12913
12914     if (   cmailMailedMove
12915         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12916         return TRUE;            /* Allow free viewing  */
12917     }
12918
12919     /* Unregister move to ensure that we don't leave RegisterMove        */
12920     /* with the move registered when the conditions for registering no   */
12921     /* longer hold                                                       */
12922     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12923         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12924         nCmailMovesRegistered --;
12925
12926         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12927           {
12928               free(cmailCommentList[lastLoadGameNumber - 1]);
12929               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12930           }
12931     }
12932
12933     if (cmailOldMove == -1) {
12934         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12935         return FALSE;
12936     }
12937
12938     if (currentMove > cmailOldMove + 1) {
12939         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12940         return FALSE;
12941     }
12942
12943     if (currentMove < cmailOldMove) {
12944         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12945         return FALSE;
12946     }
12947
12948     if (forwardMostMove > currentMove) {
12949         /* Silently truncate extra moves */
12950         TruncateGame();
12951     }
12952
12953     if (   (currentMove == cmailOldMove + 1)
12954         || (   (currentMove == cmailOldMove)
12955             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12956                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12957         if (gameInfo.result != GameUnfinished) {
12958             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12959         }
12960
12961         if (commentList[currentMove] != NULL) {
12962             cmailCommentList[lastLoadGameNumber - 1]
12963               = StrSave(commentList[currentMove]);
12964         }
12965         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12966
12967         if (appData.debugMode)
12968           fprintf(debugFP, "Saving %s for game %d\n",
12969                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12970
12971         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12972
12973         f = fopen(string, "w");
12974         if (appData.oldSaveStyle) {
12975             SaveGameOldStyle(f); /* also closes the file */
12976
12977             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12978             f = fopen(string, "w");
12979             SavePosition(f, 0, NULL); /* also closes the file */
12980         } else {
12981             fprintf(f, "{--------------\n");
12982             PrintPosition(f, currentMove);
12983             fprintf(f, "--------------}\n\n");
12984
12985             SaveGame(f, 0, NULL); /* also closes the file*/
12986         }
12987
12988         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12989         nCmailMovesRegistered ++;
12990     } else if (nCmailGames == 1) {
12991         DisplayError(_("You have not made a move yet"), 0);
12992         return FALSE;
12993     }
12994
12995     return TRUE;
12996 }
12997
12998 void
12999 MailMoveEvent ()
13000 {
13001 #if !WIN32
13002     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13003     FILE *commandOutput;
13004     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13005     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13006     int nBuffers;
13007     int i;
13008     int archived;
13009     char *arcDir;
13010
13011     if (! cmailMsgLoaded) {
13012         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13013         return;
13014     }
13015
13016     if (nCmailGames == nCmailResults) {
13017         DisplayError(_("No unfinished games"), 0);
13018         return;
13019     }
13020
13021 #if CMAIL_PROHIBIT_REMAIL
13022     if (cmailMailedMove) {
13023       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);
13024         DisplayError(msg, 0);
13025         return;
13026     }
13027 #endif
13028
13029     if (! (cmailMailedMove || RegisterMove())) return;
13030
13031     if (   cmailMailedMove
13032         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13033       snprintf(string, MSG_SIZ, partCommandString,
13034                appData.debugMode ? " -v" : "", appData.cmailGameName);
13035         commandOutput = popen(string, "r");
13036
13037         if (commandOutput == NULL) {
13038             DisplayError(_("Failed to invoke cmail"), 0);
13039         } else {
13040             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13041                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13042             }
13043             if (nBuffers > 1) {
13044                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13045                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13046                 nBytes = MSG_SIZ - 1;
13047             } else {
13048                 (void) memcpy(msg, buffer, nBytes);
13049             }
13050             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13051
13052             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13053                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13054
13055                 archived = TRUE;
13056                 for (i = 0; i < nCmailGames; i ++) {
13057                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13058                         archived = FALSE;
13059                     }
13060                 }
13061                 if (   archived
13062                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13063                         != NULL)) {
13064                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13065                            arcDir,
13066                            appData.cmailGameName,
13067                            gameInfo.date);
13068                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13069                     cmailMsgLoaded = FALSE;
13070                 }
13071             }
13072
13073             DisplayInformation(msg);
13074             pclose(commandOutput);
13075         }
13076     } else {
13077         if ((*cmailMsg) != '\0') {
13078             DisplayInformation(cmailMsg);
13079         }
13080     }
13081
13082     return;
13083 #endif /* !WIN32 */
13084 }
13085
13086 char *
13087 CmailMsg ()
13088 {
13089 #if WIN32
13090     return NULL;
13091 #else
13092     int  prependComma = 0;
13093     char number[5];
13094     char string[MSG_SIZ];       /* Space for game-list */
13095     int  i;
13096
13097     if (!cmailMsgLoaded) return "";
13098
13099     if (cmailMailedMove) {
13100       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13101     } else {
13102         /* Create a list of games left */
13103       snprintf(string, MSG_SIZ, "[");
13104         for (i = 0; i < nCmailGames; i ++) {
13105             if (! (   cmailMoveRegistered[i]
13106                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13107                 if (prependComma) {
13108                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13109                 } else {
13110                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13111                     prependComma = 1;
13112                 }
13113
13114                 strcat(string, number);
13115             }
13116         }
13117         strcat(string, "]");
13118
13119         if (nCmailMovesRegistered + nCmailResults == 0) {
13120             switch (nCmailGames) {
13121               case 1:
13122                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13123                 break;
13124
13125               case 2:
13126                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13127                 break;
13128
13129               default:
13130                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13131                          nCmailGames);
13132                 break;
13133             }
13134         } else {
13135             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13136               case 1:
13137                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13138                          string);
13139                 break;
13140
13141               case 0:
13142                 if (nCmailResults == nCmailGames) {
13143                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13144                 } else {
13145                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13146                 }
13147                 break;
13148
13149               default:
13150                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13151                          string);
13152             }
13153         }
13154     }
13155     return cmailMsg;
13156 #endif /* WIN32 */
13157 }
13158
13159 void
13160 ResetGameEvent ()
13161 {
13162     if (gameMode == Training)
13163       SetTrainingModeOff();
13164
13165     Reset(TRUE, TRUE);
13166     cmailMsgLoaded = FALSE;
13167     if (appData.icsActive) {
13168       SendToICS(ics_prefix);
13169       SendToICS("refresh\n");
13170     }
13171 }
13172
13173 void
13174 ExitEvent (int status)
13175 {
13176     exiting++;
13177     if (exiting > 2) {
13178       /* Give up on clean exit */
13179       exit(status);
13180     }
13181     if (exiting > 1) {
13182       /* Keep trying for clean exit */
13183       return;
13184     }
13185
13186     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13187
13188     if (telnetISR != NULL) {
13189       RemoveInputSource(telnetISR);
13190     }
13191     if (icsPR != NoProc) {
13192       DestroyChildProcess(icsPR, TRUE);
13193     }
13194
13195     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13196     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13197
13198     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13199     /* make sure this other one finishes before killing it!                  */
13200     if(endingGame) { int count = 0;
13201         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13202         while(endingGame && count++ < 10) DoSleep(1);
13203         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13204     }
13205
13206     /* Kill off chess programs */
13207     if (first.pr != NoProc) {
13208         ExitAnalyzeMode();
13209
13210         DoSleep( appData.delayBeforeQuit );
13211         SendToProgram("quit\n", &first);
13212         DoSleep( appData.delayAfterQuit );
13213         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13214     }
13215     if (second.pr != NoProc) {
13216         DoSleep( appData.delayBeforeQuit );
13217         SendToProgram("quit\n", &second);
13218         DoSleep( appData.delayAfterQuit );
13219         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13220     }
13221     if (first.isr != NULL) {
13222         RemoveInputSource(first.isr);
13223     }
13224     if (second.isr != NULL) {
13225         RemoveInputSource(second.isr);
13226     }
13227
13228     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13229     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13230
13231     ShutDownFrontEnd();
13232     exit(status);
13233 }
13234
13235 void
13236 PauseEvent ()
13237 {
13238     if (appData.debugMode)
13239         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13240     if (pausing) {
13241         pausing = FALSE;
13242         ModeHighlight();
13243         if (gameMode == MachinePlaysWhite ||
13244             gameMode == MachinePlaysBlack) {
13245             StartClocks();
13246         } else {
13247             DisplayBothClocks();
13248         }
13249         if (gameMode == PlayFromGameFile) {
13250             if (appData.timeDelay >= 0)
13251                 AutoPlayGameLoop();
13252         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13253             Reset(FALSE, TRUE);
13254             SendToICS(ics_prefix);
13255             SendToICS("refresh\n");
13256         } else if (currentMove < forwardMostMove) {
13257             ForwardInner(forwardMostMove);
13258         }
13259         pauseExamInvalid = FALSE;
13260     } else {
13261         switch (gameMode) {
13262           default:
13263             return;
13264           case IcsExamining:
13265             pauseExamForwardMostMove = forwardMostMove;
13266             pauseExamInvalid = FALSE;
13267             /* fall through */
13268           case IcsObserving:
13269           case IcsPlayingWhite:
13270           case IcsPlayingBlack:
13271             pausing = TRUE;
13272             ModeHighlight();
13273             return;
13274           case PlayFromGameFile:
13275             (void) StopLoadGameTimer();
13276             pausing = TRUE;
13277             ModeHighlight();
13278             break;
13279           case BeginningOfGame:
13280             if (appData.icsActive) return;
13281             /* else fall through */
13282           case MachinePlaysWhite:
13283           case MachinePlaysBlack:
13284           case TwoMachinesPlay:
13285             if (forwardMostMove == 0)
13286               return;           /* don't pause if no one has moved */
13287             if ((gameMode == MachinePlaysWhite &&
13288                  !WhiteOnMove(forwardMostMove)) ||
13289                 (gameMode == MachinePlaysBlack &&
13290                  WhiteOnMove(forwardMostMove))) {
13291                 StopClocks();
13292             }
13293             pausing = TRUE;
13294             ModeHighlight();
13295             break;
13296         }
13297     }
13298 }
13299
13300 void
13301 EditCommentEvent ()
13302 {
13303     char title[MSG_SIZ];
13304
13305     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13306       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13307     } else {
13308       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13309                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13310                parseList[currentMove - 1]);
13311     }
13312
13313     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13314 }
13315
13316
13317 void
13318 EditTagsEvent ()
13319 {
13320     char *tags = PGNTags(&gameInfo);
13321     bookUp = FALSE;
13322     EditTagsPopUp(tags, NULL);
13323     free(tags);
13324 }
13325
13326 void
13327 AnalyzeModeEvent ()
13328 {
13329     if (appData.noChessProgram || gameMode == AnalyzeMode)
13330       return;
13331
13332     if (gameMode != AnalyzeFile) {
13333         if (!appData.icsEngineAnalyze) {
13334                EditGameEvent();
13335                if (gameMode != EditGame) return;
13336         }
13337         ResurrectChessProgram();
13338         SendToProgram("analyze\n", &first);
13339         first.analyzing = TRUE;
13340         /*first.maybeThinking = TRUE;*/
13341         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13342         EngineOutputPopUp();
13343     }
13344     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13345     pausing = FALSE;
13346     ModeHighlight();
13347     SetGameInfo();
13348
13349     StartAnalysisClock();
13350     GetTimeMark(&lastNodeCountTime);
13351     lastNodeCount = 0;
13352 }
13353
13354 void
13355 AnalyzeFileEvent ()
13356 {
13357     if (appData.noChessProgram || gameMode == AnalyzeFile)
13358       return;
13359
13360     if (gameMode != AnalyzeMode) {
13361         EditGameEvent();
13362         if (gameMode != EditGame) return;
13363         ResurrectChessProgram();
13364         SendToProgram("analyze\n", &first);
13365         first.analyzing = TRUE;
13366         /*first.maybeThinking = TRUE;*/
13367         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13368         EngineOutputPopUp();
13369     }
13370     gameMode = AnalyzeFile;
13371     pausing = FALSE;
13372     ModeHighlight();
13373     SetGameInfo();
13374
13375     StartAnalysisClock();
13376     GetTimeMark(&lastNodeCountTime);
13377     lastNodeCount = 0;
13378     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13379 }
13380
13381 void
13382 MachineWhiteEvent ()
13383 {
13384     char buf[MSG_SIZ];
13385     char *bookHit = NULL;
13386
13387     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13388       return;
13389
13390
13391     if (gameMode == PlayFromGameFile ||
13392         gameMode == TwoMachinesPlay  ||
13393         gameMode == Training         ||
13394         gameMode == AnalyzeMode      ||
13395         gameMode == EndOfGame)
13396         EditGameEvent();
13397
13398     if (gameMode == EditPosition)
13399         EditPositionDone(TRUE);
13400
13401     if (!WhiteOnMove(currentMove)) {
13402         DisplayError(_("It is not White's turn"), 0);
13403         return;
13404     }
13405
13406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13407       ExitAnalyzeMode();
13408
13409     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13410         gameMode == AnalyzeFile)
13411         TruncateGame();
13412
13413     ResurrectChessProgram();    /* in case it isn't running */
13414     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13415         gameMode = MachinePlaysWhite;
13416         ResetClocks();
13417     } else
13418     gameMode = MachinePlaysWhite;
13419     pausing = FALSE;
13420     ModeHighlight();
13421     SetGameInfo();
13422     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13423     DisplayTitle(buf);
13424     if (first.sendName) {
13425       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13426       SendToProgram(buf, &first);
13427     }
13428     if (first.sendTime) {
13429       if (first.useColors) {
13430         SendToProgram("black\n", &first); /*gnu kludge*/
13431       }
13432       SendTimeRemaining(&first, TRUE);
13433     }
13434     if (first.useColors) {
13435       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13436     }
13437     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13438     SetMachineThinkingEnables();
13439     first.maybeThinking = TRUE;
13440     StartClocks();
13441     firstMove = FALSE;
13442
13443     if (appData.autoFlipView && !flipView) {
13444       flipView = !flipView;
13445       DrawPosition(FALSE, NULL);
13446       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13447     }
13448
13449     if(bookHit) { // [HGM] book: simulate book reply
13450         static char bookMove[MSG_SIZ]; // a bit generous?
13451
13452         programStats.nodes = programStats.depth = programStats.time =
13453         programStats.score = programStats.got_only_move = 0;
13454         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13455
13456         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13457         strcat(bookMove, bookHit);
13458         HandleMachineMove(bookMove, &first);
13459     }
13460 }
13461
13462 void
13463 MachineBlackEvent ()
13464 {
13465   char buf[MSG_SIZ];
13466   char *bookHit = NULL;
13467
13468     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13469         return;
13470
13471
13472     if (gameMode == PlayFromGameFile ||
13473         gameMode == TwoMachinesPlay  ||
13474         gameMode == Training         ||
13475         gameMode == AnalyzeMode      ||
13476         gameMode == EndOfGame)
13477         EditGameEvent();
13478
13479     if (gameMode == EditPosition)
13480         EditPositionDone(TRUE);
13481
13482     if (WhiteOnMove(currentMove)) {
13483         DisplayError(_("It is not Black's turn"), 0);
13484         return;
13485     }
13486
13487     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13488       ExitAnalyzeMode();
13489
13490     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13491         gameMode == AnalyzeFile)
13492         TruncateGame();
13493
13494     ResurrectChessProgram();    /* in case it isn't running */
13495     gameMode = MachinePlaysBlack;
13496     pausing = FALSE;
13497     ModeHighlight();
13498     SetGameInfo();
13499     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13500     DisplayTitle(buf);
13501     if (first.sendName) {
13502       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13503       SendToProgram(buf, &first);
13504     }
13505     if (first.sendTime) {
13506       if (first.useColors) {
13507         SendToProgram("white\n", &first); /*gnu kludge*/
13508       }
13509       SendTimeRemaining(&first, FALSE);
13510     }
13511     if (first.useColors) {
13512       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13513     }
13514     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13515     SetMachineThinkingEnables();
13516     first.maybeThinking = TRUE;
13517     StartClocks();
13518
13519     if (appData.autoFlipView && flipView) {
13520       flipView = !flipView;
13521       DrawPosition(FALSE, NULL);
13522       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13523     }
13524     if(bookHit) { // [HGM] book: simulate book reply
13525         static char bookMove[MSG_SIZ]; // a bit generous?
13526
13527         programStats.nodes = programStats.depth = programStats.time =
13528         programStats.score = programStats.got_only_move = 0;
13529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13530
13531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13532         strcat(bookMove, bookHit);
13533         HandleMachineMove(bookMove, &first);
13534     }
13535 }
13536
13537
13538 void
13539 DisplayTwoMachinesTitle ()
13540 {
13541     char buf[MSG_SIZ];
13542     if (appData.matchGames > 0) {
13543         if(appData.tourneyFile[0]) {
13544           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13545                    gameInfo.white, _("vs."), gameInfo.black,
13546                    nextGame+1, appData.matchGames+1,
13547                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13548         } else 
13549         if (first.twoMachinesColor[0] == 'w') {
13550           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13551                    gameInfo.white, _("vs."),  gameInfo.black,
13552                    first.matchWins, second.matchWins,
13553                    matchGame - 1 - (first.matchWins + second.matchWins));
13554         } else {
13555           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13556                    gameInfo.white, _("vs."), gameInfo.black,
13557                    second.matchWins, first.matchWins,
13558                    matchGame - 1 - (first.matchWins + second.matchWins));
13559         }
13560     } else {
13561       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13562     }
13563     DisplayTitle(buf);
13564 }
13565
13566 void
13567 SettingsMenuIfReady ()
13568 {
13569   if (second.lastPing != second.lastPong) {
13570     DisplayMessage("", _("Waiting for second chess program"));
13571     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13572     return;
13573   }
13574   ThawUI();
13575   DisplayMessage("", "");
13576   SettingsPopUp(&second);
13577 }
13578
13579 int
13580 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13581 {
13582     char buf[MSG_SIZ];
13583     if (cps->pr == NoProc) {
13584         StartChessProgram(cps);
13585         if (cps->protocolVersion == 1) {
13586           retry();
13587         } else {
13588           /* kludge: allow timeout for initial "feature" command */
13589           FreezeUI();
13590           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13591           DisplayMessage("", buf);
13592           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13593         }
13594         return 1;
13595     }
13596     return 0;
13597 }
13598
13599 void
13600 TwoMachinesEvent P((void))
13601 {
13602     int i;
13603     char buf[MSG_SIZ];
13604     ChessProgramState *onmove;
13605     char *bookHit = NULL;
13606     static int stalling = 0;
13607     TimeMark now;
13608     long wait;
13609
13610     if (appData.noChessProgram) return;
13611
13612     switch (gameMode) {
13613       case TwoMachinesPlay:
13614         return;
13615       case MachinePlaysWhite:
13616       case MachinePlaysBlack:
13617         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13618             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13619             return;
13620         }
13621         /* fall through */
13622       case BeginningOfGame:
13623       case PlayFromGameFile:
13624       case EndOfGame:
13625         EditGameEvent();
13626         if (gameMode != EditGame) return;
13627         break;
13628       case EditPosition:
13629         EditPositionDone(TRUE);
13630         break;
13631       case AnalyzeMode:
13632       case AnalyzeFile:
13633         ExitAnalyzeMode();
13634         break;
13635       case EditGame:
13636       default:
13637         break;
13638     }
13639
13640 //    forwardMostMove = currentMove;
13641     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13642
13643     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13644
13645     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13646     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13647       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13648       return;
13649     }
13650
13651     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13652         DisplayError("second engine does not play this", 0);
13653         return;
13654     }
13655
13656     if(!stalling) {
13657       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13658       SendToProgram("force\n", &second);
13659       stalling = 1;
13660       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13661       return;
13662     }
13663     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13664     if(appData.matchPause>10000 || appData.matchPause<10)
13665                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13666     wait = SubtractTimeMarks(&now, &pauseStart);
13667     if(wait < appData.matchPause) {
13668         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13669         return;
13670     }
13671     // we are now committed to starting the game
13672     stalling = 0;
13673     DisplayMessage("", "");
13674     if (startedFromSetupPosition) {
13675         SendBoard(&second, backwardMostMove);
13676     if (appData.debugMode) {
13677         fprintf(debugFP, "Two Machines\n");
13678     }
13679     }
13680     for (i = backwardMostMove; i < forwardMostMove; i++) {
13681         SendMoveToProgram(i, &second);
13682     }
13683
13684     gameMode = TwoMachinesPlay;
13685     pausing = FALSE;
13686     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13687     SetGameInfo();
13688     DisplayTwoMachinesTitle();
13689     firstMove = TRUE;
13690     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13691         onmove = &first;
13692     } else {
13693         onmove = &second;
13694     }
13695     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13696     SendToProgram(first.computerString, &first);
13697     if (first.sendName) {
13698       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13699       SendToProgram(buf, &first);
13700     }
13701     SendToProgram(second.computerString, &second);
13702     if (second.sendName) {
13703       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13704       SendToProgram(buf, &second);
13705     }
13706
13707     ResetClocks();
13708     if (!first.sendTime || !second.sendTime) {
13709         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13710         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13711     }
13712     if (onmove->sendTime) {
13713       if (onmove->useColors) {
13714         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13715       }
13716       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13717     }
13718     if (onmove->useColors) {
13719       SendToProgram(onmove->twoMachinesColor, onmove);
13720     }
13721     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13722 //    SendToProgram("go\n", onmove);
13723     onmove->maybeThinking = TRUE;
13724     SetMachineThinkingEnables();
13725
13726     StartClocks();
13727
13728     if(bookHit) { // [HGM] book: simulate book reply
13729         static char bookMove[MSG_SIZ]; // a bit generous?
13730
13731         programStats.nodes = programStats.depth = programStats.time =
13732         programStats.score = programStats.got_only_move = 0;
13733         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13734
13735         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13736         strcat(bookMove, bookHit);
13737         savedMessage = bookMove; // args for deferred call
13738         savedState = onmove;
13739         ScheduleDelayedEvent(DeferredBookMove, 1);
13740     }
13741 }
13742
13743 void
13744 TrainingEvent ()
13745 {
13746     if (gameMode == Training) {
13747       SetTrainingModeOff();
13748       gameMode = PlayFromGameFile;
13749       DisplayMessage("", _("Training mode off"));
13750     } else {
13751       gameMode = Training;
13752       animateTraining = appData.animate;
13753
13754       /* make sure we are not already at the end of the game */
13755       if (currentMove < forwardMostMove) {
13756         SetTrainingModeOn();
13757         DisplayMessage("", _("Training mode on"));
13758       } else {
13759         gameMode = PlayFromGameFile;
13760         DisplayError(_("Already at end of game"), 0);
13761       }
13762     }
13763     ModeHighlight();
13764 }
13765
13766 void
13767 IcsClientEvent ()
13768 {
13769     if (!appData.icsActive) return;
13770     switch (gameMode) {
13771       case IcsPlayingWhite:
13772       case IcsPlayingBlack:
13773       case IcsObserving:
13774       case IcsIdle:
13775       case BeginningOfGame:
13776       case IcsExamining:
13777         return;
13778
13779       case EditGame:
13780         break;
13781
13782       case EditPosition:
13783         EditPositionDone(TRUE);
13784         break;
13785
13786       case AnalyzeMode:
13787       case AnalyzeFile:
13788         ExitAnalyzeMode();
13789         break;
13790
13791       default:
13792         EditGameEvent();
13793         break;
13794     }
13795
13796     gameMode = IcsIdle;
13797     ModeHighlight();
13798     return;
13799 }
13800
13801 void
13802 EditGameEvent ()
13803 {
13804     int i;
13805
13806     switch (gameMode) {
13807       case Training:
13808         SetTrainingModeOff();
13809         break;
13810       case MachinePlaysWhite:
13811       case MachinePlaysBlack:
13812       case BeginningOfGame:
13813         SendToProgram("force\n", &first);
13814         SetUserThinkingEnables();
13815         break;
13816       case PlayFromGameFile:
13817         (void) StopLoadGameTimer();
13818         if (gameFileFP != NULL) {
13819             gameFileFP = NULL;
13820         }
13821         break;
13822       case EditPosition:
13823         EditPositionDone(TRUE);
13824         break;
13825       case AnalyzeMode:
13826       case AnalyzeFile:
13827         ExitAnalyzeMode();
13828         SendToProgram("force\n", &first);
13829         break;
13830       case TwoMachinesPlay:
13831         GameEnds(EndOfFile, NULL, GE_PLAYER);
13832         ResurrectChessProgram();
13833         SetUserThinkingEnables();
13834         break;
13835       case EndOfGame:
13836         ResurrectChessProgram();
13837         break;
13838       case IcsPlayingBlack:
13839       case IcsPlayingWhite:
13840         DisplayError(_("Warning: You are still playing a game"), 0);
13841         break;
13842       case IcsObserving:
13843         DisplayError(_("Warning: You are still observing a game"), 0);
13844         break;
13845       case IcsExamining:
13846         DisplayError(_("Warning: You are still examining a game"), 0);
13847         break;
13848       case IcsIdle:
13849         break;
13850       case EditGame:
13851       default:
13852         return;
13853     }
13854
13855     pausing = FALSE;
13856     StopClocks();
13857     first.offeredDraw = second.offeredDraw = 0;
13858
13859     if (gameMode == PlayFromGameFile) {
13860         whiteTimeRemaining = timeRemaining[0][currentMove];
13861         blackTimeRemaining = timeRemaining[1][currentMove];
13862         DisplayTitle("");
13863     }
13864
13865     if (gameMode == MachinePlaysWhite ||
13866         gameMode == MachinePlaysBlack ||
13867         gameMode == TwoMachinesPlay ||
13868         gameMode == EndOfGame) {
13869         i = forwardMostMove;
13870         while (i > currentMove) {
13871             SendToProgram("undo\n", &first);
13872             i--;
13873         }
13874         if(!adjustedClock) {
13875         whiteTimeRemaining = timeRemaining[0][currentMove];
13876         blackTimeRemaining = timeRemaining[1][currentMove];
13877         DisplayBothClocks();
13878         }
13879         if (whiteFlag || blackFlag) {
13880             whiteFlag = blackFlag = 0;
13881         }
13882         DisplayTitle("");
13883     }
13884
13885     gameMode = EditGame;
13886     ModeHighlight();
13887     SetGameInfo();
13888 }
13889
13890
13891 void
13892 EditPositionEvent ()
13893 {
13894     if (gameMode == EditPosition) {
13895         EditGameEvent();
13896         return;
13897     }
13898
13899     EditGameEvent();
13900     if (gameMode != EditGame) return;
13901
13902     gameMode = EditPosition;
13903     ModeHighlight();
13904     SetGameInfo();
13905     if (currentMove > 0)
13906       CopyBoard(boards[0], boards[currentMove]);
13907
13908     blackPlaysFirst = !WhiteOnMove(currentMove);
13909     ResetClocks();
13910     currentMove = forwardMostMove = backwardMostMove = 0;
13911     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13912     DisplayMove(-1);
13913     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13914 }
13915
13916 void
13917 ExitAnalyzeMode ()
13918 {
13919     /* [DM] icsEngineAnalyze - possible call from other functions */
13920     if (appData.icsEngineAnalyze) {
13921         appData.icsEngineAnalyze = FALSE;
13922
13923         DisplayMessage("",_("Close ICS engine analyze..."));
13924     }
13925     if (first.analysisSupport && first.analyzing) {
13926       SendToProgram("exit\n", &first);
13927       first.analyzing = FALSE;
13928     }
13929     thinkOutput[0] = NULLCHAR;
13930 }
13931
13932 void
13933 EditPositionDone (Boolean fakeRights)
13934 {
13935     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13936
13937     startedFromSetupPosition = TRUE;
13938     InitChessProgram(&first, FALSE);
13939     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13940       boards[0][EP_STATUS] = EP_NONE;
13941       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13942       if(boards[0][0][BOARD_WIDTH>>1] == king) {
13943         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
13944         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13945       } else boards[0][CASTLING][2] = NoRights;
13946       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13947         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
13948         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13949       } else boards[0][CASTLING][5] = NoRights;
13950       if(gameInfo.variant = VariantSChess) {
13951         int i;
13952         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
13953           boards[0][VIRGIN][i] = 0;
13954           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
13955           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
13956         }
13957       }
13958     }
13959     SendToProgram("force\n", &first);
13960     if (blackPlaysFirst) {
13961         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13962         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13963         currentMove = forwardMostMove = backwardMostMove = 1;
13964         CopyBoard(boards[1], boards[0]);
13965     } else {
13966         currentMove = forwardMostMove = backwardMostMove = 0;
13967     }
13968     SendBoard(&first, forwardMostMove);
13969     if (appData.debugMode) {
13970         fprintf(debugFP, "EditPosDone\n");
13971     }
13972     DisplayTitle("");
13973     DisplayMessage("", "");
13974     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13975     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13976     gameMode = EditGame;
13977     ModeHighlight();
13978     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13979     ClearHighlights(); /* [AS] */
13980 }
13981
13982 /* Pause for `ms' milliseconds */
13983 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13984 void
13985 TimeDelay (long ms)
13986 {
13987     TimeMark m1, m2;
13988
13989     GetTimeMark(&m1);
13990     do {
13991         GetTimeMark(&m2);
13992     } while (SubtractTimeMarks(&m2, &m1) < ms);
13993 }
13994
13995 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13996 void
13997 SendMultiLineToICS (char *buf)
13998 {
13999     char temp[MSG_SIZ+1], *p;
14000     int len;
14001
14002     len = strlen(buf);
14003     if (len > MSG_SIZ)
14004       len = MSG_SIZ;
14005
14006     strncpy(temp, buf, len);
14007     temp[len] = 0;
14008
14009     p = temp;
14010     while (*p) {
14011         if (*p == '\n' || *p == '\r')
14012           *p = ' ';
14013         ++p;
14014     }
14015
14016     strcat(temp, "\n");
14017     SendToICS(temp);
14018     SendToPlayer(temp, strlen(temp));
14019 }
14020
14021 void
14022 SetWhiteToPlayEvent ()
14023 {
14024     if (gameMode == EditPosition) {
14025         blackPlaysFirst = FALSE;
14026         DisplayBothClocks();    /* works because currentMove is 0 */
14027     } else if (gameMode == IcsExamining) {
14028         SendToICS(ics_prefix);
14029         SendToICS("tomove white\n");
14030     }
14031 }
14032
14033 void
14034 SetBlackToPlayEvent ()
14035 {
14036     if (gameMode == EditPosition) {
14037         blackPlaysFirst = TRUE;
14038         currentMove = 1;        /* kludge */
14039         DisplayBothClocks();
14040         currentMove = 0;
14041     } else if (gameMode == IcsExamining) {
14042         SendToICS(ics_prefix);
14043         SendToICS("tomove black\n");
14044     }
14045 }
14046
14047 void
14048 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14049 {
14050     char buf[MSG_SIZ];
14051     ChessSquare piece = boards[0][y][x];
14052
14053     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14054
14055     switch (selection) {
14056       case ClearBoard:
14057         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14058             SendToICS(ics_prefix);
14059             SendToICS("bsetup clear\n");
14060         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14061             SendToICS(ics_prefix);
14062             SendToICS("clearboard\n");
14063         } else {
14064             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14065                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14066                 for (y = 0; y < BOARD_HEIGHT; y++) {
14067                     if (gameMode == IcsExamining) {
14068                         if (boards[currentMove][y][x] != EmptySquare) {
14069                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14070                                     AAA + x, ONE + y);
14071                             SendToICS(buf);
14072                         }
14073                     } else {
14074                         boards[0][y][x] = p;
14075                     }
14076                 }
14077             }
14078         }
14079         if (gameMode == EditPosition) {
14080             DrawPosition(FALSE, boards[0]);
14081         }
14082         break;
14083
14084       case WhitePlay:
14085         SetWhiteToPlayEvent();
14086         break;
14087
14088       case BlackPlay:
14089         SetBlackToPlayEvent();
14090         break;
14091
14092       case EmptySquare:
14093         if (gameMode == IcsExamining) {
14094             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14095             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14096             SendToICS(buf);
14097         } else {
14098             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14099                 if(x == BOARD_LEFT-2) {
14100                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14101                     boards[0][y][1] = 0;
14102                 } else
14103                 if(x == BOARD_RGHT+1) {
14104                     if(y >= gameInfo.holdingsSize) break;
14105                     boards[0][y][BOARD_WIDTH-2] = 0;
14106                 } else break;
14107             }
14108             boards[0][y][x] = EmptySquare;
14109             DrawPosition(FALSE, boards[0]);
14110         }
14111         break;
14112
14113       case PromotePiece:
14114         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14115            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14116             selection = (ChessSquare) (PROMOTED piece);
14117         } else if(piece == EmptySquare) selection = WhiteSilver;
14118         else selection = (ChessSquare)((int)piece - 1);
14119         goto defaultlabel;
14120
14121       case DemotePiece:
14122         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14123            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14124             selection = (ChessSquare) (DEMOTED piece);
14125         } else if(piece == EmptySquare) selection = BlackSilver;
14126         else selection = (ChessSquare)((int)piece + 1);
14127         goto defaultlabel;
14128
14129       case WhiteQueen:
14130       case BlackQueen:
14131         if(gameInfo.variant == VariantShatranj ||
14132            gameInfo.variant == VariantXiangqi  ||
14133            gameInfo.variant == VariantCourier  ||
14134            gameInfo.variant == VariantMakruk     )
14135             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14136         goto defaultlabel;
14137
14138       case WhiteKing:
14139       case BlackKing:
14140         if(gameInfo.variant == VariantXiangqi)
14141             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14142         if(gameInfo.variant == VariantKnightmate)
14143             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14144       default:
14145         defaultlabel:
14146         if (gameMode == IcsExamining) {
14147             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14148             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14149                      PieceToChar(selection), AAA + x, ONE + y);
14150             SendToICS(buf);
14151         } else {
14152             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14153                 int n;
14154                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14155                     n = PieceToNumber(selection - BlackPawn);
14156                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14157                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14158                     boards[0][BOARD_HEIGHT-1-n][1]++;
14159                 } else
14160                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14161                     n = PieceToNumber(selection);
14162                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14163                     boards[0][n][BOARD_WIDTH-1] = selection;
14164                     boards[0][n][BOARD_WIDTH-2]++;
14165                 }
14166             } else
14167             boards[0][y][x] = selection;
14168             DrawPosition(TRUE, boards[0]);
14169             ClearHighlights();
14170             fromX = fromY = -1;
14171         }
14172         break;
14173     }
14174 }
14175
14176
14177 void
14178 DropMenuEvent (ChessSquare selection, int x, int y)
14179 {
14180     ChessMove moveType;
14181
14182     switch (gameMode) {
14183       case IcsPlayingWhite:
14184       case MachinePlaysBlack:
14185         if (!WhiteOnMove(currentMove)) {
14186             DisplayMoveError(_("It is Black's turn"));
14187             return;
14188         }
14189         moveType = WhiteDrop;
14190         break;
14191       case IcsPlayingBlack:
14192       case MachinePlaysWhite:
14193         if (WhiteOnMove(currentMove)) {
14194             DisplayMoveError(_("It is White's turn"));
14195             return;
14196         }
14197         moveType = BlackDrop;
14198         break;
14199       case EditGame:
14200         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14201         break;
14202       default:
14203         return;
14204     }
14205
14206     if (moveType == BlackDrop && selection < BlackPawn) {
14207       selection = (ChessSquare) ((int) selection
14208                                  + (int) BlackPawn - (int) WhitePawn);
14209     }
14210     if (boards[currentMove][y][x] != EmptySquare) {
14211         DisplayMoveError(_("That square is occupied"));
14212         return;
14213     }
14214
14215     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14216 }
14217
14218 void
14219 AcceptEvent ()
14220 {
14221     /* Accept a pending offer of any kind from opponent */
14222
14223     if (appData.icsActive) {
14224         SendToICS(ics_prefix);
14225         SendToICS("accept\n");
14226     } else if (cmailMsgLoaded) {
14227         if (currentMove == cmailOldMove &&
14228             commentList[cmailOldMove] != NULL &&
14229             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14230                    "Black offers a draw" : "White offers a draw")) {
14231             TruncateGame();
14232             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14233             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14234         } else {
14235             DisplayError(_("There is no pending offer on this move"), 0);
14236             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14237         }
14238     } else {
14239         /* Not used for offers from chess program */
14240     }
14241 }
14242
14243 void
14244 DeclineEvent ()
14245 {
14246     /* Decline a pending offer of any kind from opponent */
14247
14248     if (appData.icsActive) {
14249         SendToICS(ics_prefix);
14250         SendToICS("decline\n");
14251     } else if (cmailMsgLoaded) {
14252         if (currentMove == cmailOldMove &&
14253             commentList[cmailOldMove] != NULL &&
14254             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14255                    "Black offers a draw" : "White offers a draw")) {
14256 #ifdef NOTDEF
14257             AppendComment(cmailOldMove, "Draw declined", TRUE);
14258             DisplayComment(cmailOldMove - 1, "Draw declined");
14259 #endif /*NOTDEF*/
14260         } else {
14261             DisplayError(_("There is no pending offer on this move"), 0);
14262         }
14263     } else {
14264         /* Not used for offers from chess program */
14265     }
14266 }
14267
14268 void
14269 RematchEvent ()
14270 {
14271     /* Issue ICS rematch command */
14272     if (appData.icsActive) {
14273         SendToICS(ics_prefix);
14274         SendToICS("rematch\n");
14275     }
14276 }
14277
14278 void
14279 CallFlagEvent ()
14280 {
14281     /* Call your opponent's flag (claim a win on time) */
14282     if (appData.icsActive) {
14283         SendToICS(ics_prefix);
14284         SendToICS("flag\n");
14285     } else {
14286         switch (gameMode) {
14287           default:
14288             return;
14289           case MachinePlaysWhite:
14290             if (whiteFlag) {
14291                 if (blackFlag)
14292                   GameEnds(GameIsDrawn, "Both players ran out of time",
14293                            GE_PLAYER);
14294                 else
14295                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14296             } else {
14297                 DisplayError(_("Your opponent is not out of time"), 0);
14298             }
14299             break;
14300           case MachinePlaysBlack:
14301             if (blackFlag) {
14302                 if (whiteFlag)
14303                   GameEnds(GameIsDrawn, "Both players ran out of time",
14304                            GE_PLAYER);
14305                 else
14306                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14307             } else {
14308                 DisplayError(_("Your opponent is not out of time"), 0);
14309             }
14310             break;
14311         }
14312     }
14313 }
14314
14315 void
14316 ClockClick (int which)
14317 {       // [HGM] code moved to back-end from winboard.c
14318         if(which) { // black clock
14319           if (gameMode == EditPosition || gameMode == IcsExamining) {
14320             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14321             SetBlackToPlayEvent();
14322           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14323           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14324           } else if (shiftKey) {
14325             AdjustClock(which, -1);
14326           } else if (gameMode == IcsPlayingWhite ||
14327                      gameMode == MachinePlaysBlack) {
14328             CallFlagEvent();
14329           }
14330         } else { // white clock
14331           if (gameMode == EditPosition || gameMode == IcsExamining) {
14332             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14333             SetWhiteToPlayEvent();
14334           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14335           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14336           } else if (shiftKey) {
14337             AdjustClock(which, -1);
14338           } else if (gameMode == IcsPlayingBlack ||
14339                    gameMode == MachinePlaysWhite) {
14340             CallFlagEvent();
14341           }
14342         }
14343 }
14344
14345 void
14346 DrawEvent ()
14347 {
14348     /* Offer draw or accept pending draw offer from opponent */
14349
14350     if (appData.icsActive) {
14351         /* Note: tournament rules require draw offers to be
14352            made after you make your move but before you punch
14353            your clock.  Currently ICS doesn't let you do that;
14354            instead, you immediately punch your clock after making
14355            a move, but you can offer a draw at any time. */
14356
14357         SendToICS(ics_prefix);
14358         SendToICS("draw\n");
14359         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14360     } else if (cmailMsgLoaded) {
14361         if (currentMove == cmailOldMove &&
14362             commentList[cmailOldMove] != NULL &&
14363             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14364                    "Black offers a draw" : "White offers a draw")) {
14365             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14366             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14367         } else if (currentMove == cmailOldMove + 1) {
14368             char *offer = WhiteOnMove(cmailOldMove) ?
14369               "White offers a draw" : "Black offers a draw";
14370             AppendComment(currentMove, offer, TRUE);
14371             DisplayComment(currentMove - 1, offer);
14372             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14373         } else {
14374             DisplayError(_("You must make your move before offering a draw"), 0);
14375             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14376         }
14377     } else if (first.offeredDraw) {
14378         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14379     } else {
14380         if (first.sendDrawOffers) {
14381             SendToProgram("draw\n", &first);
14382             userOfferedDraw = TRUE;
14383         }
14384     }
14385 }
14386
14387 void
14388 AdjournEvent ()
14389 {
14390     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14391
14392     if (appData.icsActive) {
14393         SendToICS(ics_prefix);
14394         SendToICS("adjourn\n");
14395     } else {
14396         /* Currently GNU Chess doesn't offer or accept Adjourns */
14397     }
14398 }
14399
14400
14401 void
14402 AbortEvent ()
14403 {
14404     /* Offer Abort or accept pending Abort offer from opponent */
14405
14406     if (appData.icsActive) {
14407         SendToICS(ics_prefix);
14408         SendToICS("abort\n");
14409     } else {
14410         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14411     }
14412 }
14413
14414 void
14415 ResignEvent ()
14416 {
14417     /* Resign.  You can do this even if it's not your turn. */
14418
14419     if (appData.icsActive) {
14420         SendToICS(ics_prefix);
14421         SendToICS("resign\n");
14422     } else {
14423         switch (gameMode) {
14424           case MachinePlaysWhite:
14425             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14426             break;
14427           case MachinePlaysBlack:
14428             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14429             break;
14430           case EditGame:
14431             if (cmailMsgLoaded) {
14432                 TruncateGame();
14433                 if (WhiteOnMove(cmailOldMove)) {
14434                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14435                 } else {
14436                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14437                 }
14438                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14439             }
14440             break;
14441           default:
14442             break;
14443         }
14444     }
14445 }
14446
14447
14448 void
14449 StopObservingEvent ()
14450 {
14451     /* Stop observing current games */
14452     SendToICS(ics_prefix);
14453     SendToICS("unobserve\n");
14454 }
14455
14456 void
14457 StopExaminingEvent ()
14458 {
14459     /* Stop observing current game */
14460     SendToICS(ics_prefix);
14461     SendToICS("unexamine\n");
14462 }
14463
14464 void
14465 ForwardInner (int target)
14466 {
14467     int limit; int oldSeekGraphUp = seekGraphUp;
14468
14469     if (appData.debugMode)
14470         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14471                 target, currentMove, forwardMostMove);
14472
14473     if (gameMode == EditPosition)
14474       return;
14475
14476     seekGraphUp = FALSE;
14477     MarkTargetSquares(1);
14478
14479     if (gameMode == PlayFromGameFile && !pausing)
14480       PauseEvent();
14481
14482     if (gameMode == IcsExamining && pausing)
14483       limit = pauseExamForwardMostMove;
14484     else
14485       limit = forwardMostMove;
14486
14487     if (target > limit) target = limit;
14488
14489     if (target > 0 && moveList[target - 1][0]) {
14490         int fromX, fromY, toX, toY;
14491         toX = moveList[target - 1][2] - AAA;
14492         toY = moveList[target - 1][3] - ONE;
14493         if (moveList[target - 1][1] == '@') {
14494             if (appData.highlightLastMove) {
14495                 SetHighlights(-1, -1, toX, toY);
14496             }
14497         } else {
14498             fromX = moveList[target - 1][0] - AAA;
14499             fromY = moveList[target - 1][1] - ONE;
14500             if (target == currentMove + 1) {
14501                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14502             }
14503             if (appData.highlightLastMove) {
14504                 SetHighlights(fromX, fromY, toX, toY);
14505             }
14506         }
14507     }
14508     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14509         gameMode == Training || gameMode == PlayFromGameFile ||
14510         gameMode == AnalyzeFile) {
14511         while (currentMove < target) {
14512             SendMoveToProgram(currentMove++, &first);
14513         }
14514     } else {
14515         currentMove = target;
14516     }
14517
14518     if (gameMode == EditGame || gameMode == EndOfGame) {
14519         whiteTimeRemaining = timeRemaining[0][currentMove];
14520         blackTimeRemaining = timeRemaining[1][currentMove];
14521     }
14522     DisplayBothClocks();
14523     DisplayMove(currentMove - 1);
14524     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14525     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14526     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14527         DisplayComment(currentMove - 1, commentList[currentMove]);
14528     }
14529     ClearMap(); // [HGM] exclude: invalidate map
14530 }
14531
14532
14533 void
14534 ForwardEvent ()
14535 {
14536     if (gameMode == IcsExamining && !pausing) {
14537         SendToICS(ics_prefix);
14538         SendToICS("forward\n");
14539     } else {
14540         ForwardInner(currentMove + 1);
14541     }
14542 }
14543
14544 void
14545 ToEndEvent ()
14546 {
14547     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14548         /* to optimze, we temporarily turn off analysis mode while we feed
14549          * the remaining moves to the engine. Otherwise we get analysis output
14550          * after each move.
14551          */
14552         if (first.analysisSupport) {
14553           SendToProgram("exit\nforce\n", &first);
14554           first.analyzing = FALSE;
14555         }
14556     }
14557
14558     if (gameMode == IcsExamining && !pausing) {
14559         SendToICS(ics_prefix);
14560         SendToICS("forward 999999\n");
14561     } else {
14562         ForwardInner(forwardMostMove);
14563     }
14564
14565     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14566         /* we have fed all the moves, so reactivate analysis mode */
14567         SendToProgram("analyze\n", &first);
14568         first.analyzing = TRUE;
14569         /*first.maybeThinking = TRUE;*/
14570         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14571     }
14572 }
14573
14574 void
14575 BackwardInner (int target)
14576 {
14577     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14578
14579     if (appData.debugMode)
14580         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14581                 target, currentMove, forwardMostMove);
14582
14583     if (gameMode == EditPosition) return;
14584     seekGraphUp = FALSE;
14585     MarkTargetSquares(1);
14586     if (currentMove <= backwardMostMove) {
14587         ClearHighlights();
14588         DrawPosition(full_redraw, boards[currentMove]);
14589         return;
14590     }
14591     if (gameMode == PlayFromGameFile && !pausing)
14592       PauseEvent();
14593
14594     if (moveList[target][0]) {
14595         int fromX, fromY, toX, toY;
14596         toX = moveList[target][2] - AAA;
14597         toY = moveList[target][3] - ONE;
14598         if (moveList[target][1] == '@') {
14599             if (appData.highlightLastMove) {
14600                 SetHighlights(-1, -1, toX, toY);
14601             }
14602         } else {
14603             fromX = moveList[target][0] - AAA;
14604             fromY = moveList[target][1] - ONE;
14605             if (target == currentMove - 1) {
14606                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14607             }
14608             if (appData.highlightLastMove) {
14609                 SetHighlights(fromX, fromY, toX, toY);
14610             }
14611         }
14612     }
14613     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14614         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14615         while (currentMove > target) {
14616             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14617                 // null move cannot be undone. Reload program with move history before it.
14618                 int i;
14619                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14620                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14621                 }
14622                 SendBoard(&first, i); 
14623                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14624                 break;
14625             }
14626             SendToProgram("undo\n", &first);
14627             currentMove--;
14628         }
14629     } else {
14630         currentMove = target;
14631     }
14632
14633     if (gameMode == EditGame || gameMode == EndOfGame) {
14634         whiteTimeRemaining = timeRemaining[0][currentMove];
14635         blackTimeRemaining = timeRemaining[1][currentMove];
14636     }
14637     DisplayBothClocks();
14638     DisplayMove(currentMove - 1);
14639     DrawPosition(full_redraw, boards[currentMove]);
14640     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14641     // [HGM] PV info: routine tests if comment empty
14642     DisplayComment(currentMove - 1, commentList[currentMove]);
14643     ClearMap(); // [HGM] exclude: invalidate map
14644 }
14645
14646 void
14647 BackwardEvent ()
14648 {
14649     if (gameMode == IcsExamining && !pausing) {
14650         SendToICS(ics_prefix);
14651         SendToICS("backward\n");
14652     } else {
14653         BackwardInner(currentMove - 1);
14654     }
14655 }
14656
14657 void
14658 ToStartEvent ()
14659 {
14660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14661         /* to optimize, we temporarily turn off analysis mode while we undo
14662          * all the moves. Otherwise we get analysis output after each undo.
14663          */
14664         if (first.analysisSupport) {
14665           SendToProgram("exit\nforce\n", &first);
14666           first.analyzing = FALSE;
14667         }
14668     }
14669
14670     if (gameMode == IcsExamining && !pausing) {
14671         SendToICS(ics_prefix);
14672         SendToICS("backward 999999\n");
14673     } else {
14674         BackwardInner(backwardMostMove);
14675     }
14676
14677     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14678         /* we have fed all the moves, so reactivate analysis mode */
14679         SendToProgram("analyze\n", &first);
14680         first.analyzing = TRUE;
14681         /*first.maybeThinking = TRUE;*/
14682         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14683     }
14684 }
14685
14686 void
14687 ToNrEvent (int to)
14688 {
14689   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14690   if (to >= forwardMostMove) to = forwardMostMove;
14691   if (to <= backwardMostMove) to = backwardMostMove;
14692   if (to < currentMove) {
14693     BackwardInner(to);
14694   } else {
14695     ForwardInner(to);
14696   }
14697 }
14698
14699 void
14700 RevertEvent (Boolean annotate)
14701 {
14702     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14703         return;
14704     }
14705     if (gameMode != IcsExamining) {
14706         DisplayError(_("You are not examining a game"), 0);
14707         return;
14708     }
14709     if (pausing) {
14710         DisplayError(_("You can't revert while pausing"), 0);
14711         return;
14712     }
14713     SendToICS(ics_prefix);
14714     SendToICS("revert\n");
14715 }
14716
14717 void
14718 RetractMoveEvent ()
14719 {
14720     switch (gameMode) {
14721       case MachinePlaysWhite:
14722       case MachinePlaysBlack:
14723         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14724             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14725             return;
14726         }
14727         if (forwardMostMove < 2) return;
14728         currentMove = forwardMostMove = forwardMostMove - 2;
14729         whiteTimeRemaining = timeRemaining[0][currentMove];
14730         blackTimeRemaining = timeRemaining[1][currentMove];
14731         DisplayBothClocks();
14732         DisplayMove(currentMove - 1);
14733         ClearHighlights();/*!! could figure this out*/
14734         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14735         SendToProgram("remove\n", &first);
14736         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14737         break;
14738
14739       case BeginningOfGame:
14740       default:
14741         break;
14742
14743       case IcsPlayingWhite:
14744       case IcsPlayingBlack:
14745         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14746             SendToICS(ics_prefix);
14747             SendToICS("takeback 2\n");
14748         } else {
14749             SendToICS(ics_prefix);
14750             SendToICS("takeback 1\n");
14751         }
14752         break;
14753     }
14754 }
14755
14756 void
14757 MoveNowEvent ()
14758 {
14759     ChessProgramState *cps;
14760
14761     switch (gameMode) {
14762       case MachinePlaysWhite:
14763         if (!WhiteOnMove(forwardMostMove)) {
14764             DisplayError(_("It is your turn"), 0);
14765             return;
14766         }
14767         cps = &first;
14768         break;
14769       case MachinePlaysBlack:
14770         if (WhiteOnMove(forwardMostMove)) {
14771             DisplayError(_("It is your turn"), 0);
14772             return;
14773         }
14774         cps = &first;
14775         break;
14776       case TwoMachinesPlay:
14777         if (WhiteOnMove(forwardMostMove) ==
14778             (first.twoMachinesColor[0] == 'w')) {
14779             cps = &first;
14780         } else {
14781             cps = &second;
14782         }
14783         break;
14784       case BeginningOfGame:
14785       default:
14786         return;
14787     }
14788     SendToProgram("?\n", cps);
14789 }
14790
14791 void
14792 TruncateGameEvent ()
14793 {
14794     EditGameEvent();
14795     if (gameMode != EditGame) return;
14796     TruncateGame();
14797 }
14798
14799 void
14800 TruncateGame ()
14801 {
14802     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14803     if (forwardMostMove > currentMove) {
14804         if (gameInfo.resultDetails != NULL) {
14805             free(gameInfo.resultDetails);
14806             gameInfo.resultDetails = NULL;
14807             gameInfo.result = GameUnfinished;
14808         }
14809         forwardMostMove = currentMove;
14810         HistorySet(parseList, backwardMostMove, forwardMostMove,
14811                    currentMove-1);
14812     }
14813 }
14814
14815 void
14816 HintEvent ()
14817 {
14818     if (appData.noChessProgram) return;
14819     switch (gameMode) {
14820       case MachinePlaysWhite:
14821         if (WhiteOnMove(forwardMostMove)) {
14822             DisplayError(_("Wait until your turn"), 0);
14823             return;
14824         }
14825         break;
14826       case BeginningOfGame:
14827       case MachinePlaysBlack:
14828         if (!WhiteOnMove(forwardMostMove)) {
14829             DisplayError(_("Wait until your turn"), 0);
14830             return;
14831         }
14832         break;
14833       default:
14834         DisplayError(_("No hint available"), 0);
14835         return;
14836     }
14837     SendToProgram("hint\n", &first);
14838     hintRequested = TRUE;
14839 }
14840
14841 void
14842 BookEvent ()
14843 {
14844     if (appData.noChessProgram) return;
14845     switch (gameMode) {
14846       case MachinePlaysWhite:
14847         if (WhiteOnMove(forwardMostMove)) {
14848             DisplayError(_("Wait until your turn"), 0);
14849             return;
14850         }
14851         break;
14852       case BeginningOfGame:
14853       case MachinePlaysBlack:
14854         if (!WhiteOnMove(forwardMostMove)) {
14855             DisplayError(_("Wait until your turn"), 0);
14856             return;
14857         }
14858         break;
14859       case EditPosition:
14860         EditPositionDone(TRUE);
14861         break;
14862       case TwoMachinesPlay:
14863         return;
14864       default:
14865         break;
14866     }
14867     SendToProgram("bk\n", &first);
14868     bookOutput[0] = NULLCHAR;
14869     bookRequested = TRUE;
14870 }
14871
14872 void
14873 AboutGameEvent ()
14874 {
14875     char *tags = PGNTags(&gameInfo);
14876     TagsPopUp(tags, CmailMsg());
14877     free(tags);
14878 }
14879
14880 /* end button procedures */
14881
14882 void
14883 PrintPosition (FILE *fp, int move)
14884 {
14885     int i, j;
14886
14887     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14888         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14889             char c = PieceToChar(boards[move][i][j]);
14890             fputc(c == 'x' ? '.' : c, fp);
14891             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14892         }
14893     }
14894     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14895       fprintf(fp, "white to play\n");
14896     else
14897       fprintf(fp, "black to play\n");
14898 }
14899
14900 void
14901 PrintOpponents (FILE *fp)
14902 {
14903     if (gameInfo.white != NULL) {
14904         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14905     } else {
14906         fprintf(fp, "\n");
14907     }
14908 }
14909
14910 /* Find last component of program's own name, using some heuristics */
14911 void
14912 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14913 {
14914     char *p, *q, c;
14915     int local = (strcmp(host, "localhost") == 0);
14916     while (!local && (p = strchr(prog, ';')) != NULL) {
14917         p++;
14918         while (*p == ' ') p++;
14919         prog = p;
14920     }
14921     if (*prog == '"' || *prog == '\'') {
14922         q = strchr(prog + 1, *prog);
14923     } else {
14924         q = strchr(prog, ' ');
14925     }
14926     if (q == NULL) q = prog + strlen(prog);
14927     p = q;
14928     while (p >= prog && *p != '/' && *p != '\\') p--;
14929     p++;
14930     if(p == prog && *p == '"') p++;
14931     c = *q; *q = 0;
14932     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14933     memcpy(buf, p, q - p);
14934     buf[q - p] = NULLCHAR;
14935     if (!local) {
14936         strcat(buf, "@");
14937         strcat(buf, host);
14938     }
14939 }
14940
14941 char *
14942 TimeControlTagValue ()
14943 {
14944     char buf[MSG_SIZ];
14945     if (!appData.clockMode) {
14946       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14947     } else if (movesPerSession > 0) {
14948       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14949     } else if (timeIncrement == 0) {
14950       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14951     } else {
14952       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14953     }
14954     return StrSave(buf);
14955 }
14956
14957 void
14958 SetGameInfo ()
14959 {
14960     /* This routine is used only for certain modes */
14961     VariantClass v = gameInfo.variant;
14962     ChessMove r = GameUnfinished;
14963     char *p = NULL;
14964
14965     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14966         r = gameInfo.result;
14967         p = gameInfo.resultDetails;
14968         gameInfo.resultDetails = NULL;
14969     }
14970     ClearGameInfo(&gameInfo);
14971     gameInfo.variant = v;
14972
14973     switch (gameMode) {
14974       case MachinePlaysWhite:
14975         gameInfo.event = StrSave( appData.pgnEventHeader );
14976         gameInfo.site = StrSave(HostName());
14977         gameInfo.date = PGNDate();
14978         gameInfo.round = StrSave("-");
14979         gameInfo.white = StrSave(first.tidy);
14980         gameInfo.black = StrSave(UserName());
14981         gameInfo.timeControl = TimeControlTagValue();
14982         break;
14983
14984       case MachinePlaysBlack:
14985         gameInfo.event = StrSave( appData.pgnEventHeader );
14986         gameInfo.site = StrSave(HostName());
14987         gameInfo.date = PGNDate();
14988         gameInfo.round = StrSave("-");
14989         gameInfo.white = StrSave(UserName());
14990         gameInfo.black = StrSave(first.tidy);
14991         gameInfo.timeControl = TimeControlTagValue();
14992         break;
14993
14994       case TwoMachinesPlay:
14995         gameInfo.event = StrSave( appData.pgnEventHeader );
14996         gameInfo.site = StrSave(HostName());
14997         gameInfo.date = PGNDate();
14998         if (roundNr > 0) {
14999             char buf[MSG_SIZ];
15000             snprintf(buf, MSG_SIZ, "%d", roundNr);
15001             gameInfo.round = StrSave(buf);
15002         } else {
15003             gameInfo.round = StrSave("-");
15004         }
15005         if (first.twoMachinesColor[0] == 'w') {
15006             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15007             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15008         } else {
15009             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15010             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15011         }
15012         gameInfo.timeControl = TimeControlTagValue();
15013         break;
15014
15015       case EditGame:
15016         gameInfo.event = StrSave("Edited game");
15017         gameInfo.site = StrSave(HostName());
15018         gameInfo.date = PGNDate();
15019         gameInfo.round = StrSave("-");
15020         gameInfo.white = StrSave("-");
15021         gameInfo.black = StrSave("-");
15022         gameInfo.result = r;
15023         gameInfo.resultDetails = p;
15024         break;
15025
15026       case EditPosition:
15027         gameInfo.event = StrSave("Edited position");
15028         gameInfo.site = StrSave(HostName());
15029         gameInfo.date = PGNDate();
15030         gameInfo.round = StrSave("-");
15031         gameInfo.white = StrSave("-");
15032         gameInfo.black = StrSave("-");
15033         break;
15034
15035       case IcsPlayingWhite:
15036       case IcsPlayingBlack:
15037       case IcsObserving:
15038       case IcsExamining:
15039         break;
15040
15041       case PlayFromGameFile:
15042         gameInfo.event = StrSave("Game from non-PGN file");
15043         gameInfo.site = StrSave(HostName());
15044         gameInfo.date = PGNDate();
15045         gameInfo.round = StrSave("-");
15046         gameInfo.white = StrSave("?");
15047         gameInfo.black = StrSave("?");
15048         break;
15049
15050       default:
15051         break;
15052     }
15053 }
15054
15055 void
15056 ReplaceComment (int index, char *text)
15057 {
15058     int len;
15059     char *p;
15060     float score;
15061
15062     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15063        pvInfoList[index-1].depth == len &&
15064        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15065        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15066     while (*text == '\n') text++;
15067     len = strlen(text);
15068     while (len > 0 && text[len - 1] == '\n') len--;
15069
15070     if (commentList[index] != NULL)
15071       free(commentList[index]);
15072
15073     if (len == 0) {
15074         commentList[index] = NULL;
15075         return;
15076     }
15077   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15078       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15079       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15080     commentList[index] = (char *) malloc(len + 2);
15081     strncpy(commentList[index], text, len);
15082     commentList[index][len] = '\n';
15083     commentList[index][len + 1] = NULLCHAR;
15084   } else {
15085     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15086     char *p;
15087     commentList[index] = (char *) malloc(len + 7);
15088     safeStrCpy(commentList[index], "{\n", 3);
15089     safeStrCpy(commentList[index]+2, text, len+1);
15090     commentList[index][len+2] = NULLCHAR;
15091     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15092     strcat(commentList[index], "\n}\n");
15093   }
15094 }
15095
15096 void
15097 CrushCRs (char *text)
15098 {
15099   char *p = text;
15100   char *q = text;
15101   char ch;
15102
15103   do {
15104     ch = *p++;
15105     if (ch == '\r') continue;
15106     *q++ = ch;
15107   } while (ch != '\0');
15108 }
15109
15110 void
15111 AppendComment (int index, char *text, Boolean addBraces)
15112 /* addBraces  tells if we should add {} */
15113 {
15114     int oldlen, len;
15115     char *old;
15116
15117 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15118     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15119
15120     CrushCRs(text);
15121     while (*text == '\n') text++;
15122     len = strlen(text);
15123     while (len > 0 && text[len - 1] == '\n') len--;
15124     text[len] = NULLCHAR;
15125
15126     if (len == 0) return;
15127
15128     if (commentList[index] != NULL) {
15129       Boolean addClosingBrace = addBraces;
15130         old = commentList[index];
15131         oldlen = strlen(old);
15132         while(commentList[index][oldlen-1] ==  '\n')
15133           commentList[index][--oldlen] = NULLCHAR;
15134         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15135         safeStrCpy(commentList[index], old, oldlen + len + 6);
15136         free(old);
15137         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15138         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15139           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15140           while (*text == '\n') { text++; len--; }
15141           commentList[index][--oldlen] = NULLCHAR;
15142       }
15143         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15144         else          strcat(commentList[index], "\n");
15145         strcat(commentList[index], text);
15146         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15147         else          strcat(commentList[index], "\n");
15148     } else {
15149         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15150         if(addBraces)
15151           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15152         else commentList[index][0] = NULLCHAR;
15153         strcat(commentList[index], text);
15154         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15155         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15156     }
15157 }
15158
15159 static char *
15160 FindStr (char * text, char * sub_text)
15161 {
15162     char * result = strstr( text, sub_text );
15163
15164     if( result != NULL ) {
15165         result += strlen( sub_text );
15166     }
15167
15168     return result;
15169 }
15170
15171 /* [AS] Try to extract PV info from PGN comment */
15172 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15173 char *
15174 GetInfoFromComment (int index, char * text)
15175 {
15176     char * sep = text, *p;
15177
15178     if( text != NULL && index > 0 ) {
15179         int score = 0;
15180         int depth = 0;
15181         int time = -1, sec = 0, deci;
15182         char * s_eval = FindStr( text, "[%eval " );
15183         char * s_emt = FindStr( text, "[%emt " );
15184
15185         if( s_eval != NULL || s_emt != NULL ) {
15186             /* New style */
15187             char delim;
15188
15189             if( s_eval != NULL ) {
15190                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15191                     return text;
15192                 }
15193
15194                 if( delim != ']' ) {
15195                     return text;
15196                 }
15197             }
15198
15199             if( s_emt != NULL ) {
15200             }
15201                 return text;
15202         }
15203         else {
15204             /* We expect something like: [+|-]nnn.nn/dd */
15205             int score_lo = 0;
15206
15207             if(*text != '{') return text; // [HGM] braces: must be normal comment
15208
15209             sep = strchr( text, '/' );
15210             if( sep == NULL || sep < (text+4) ) {
15211                 return text;
15212             }
15213
15214             p = text;
15215             if(p[1] == '(') { // comment starts with PV
15216                p = strchr(p, ')'); // locate end of PV
15217                if(p == NULL || sep < p+5) return text;
15218                // at this point we have something like "{(.*) +0.23/6 ..."
15219                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15220                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15221                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15222             }
15223             time = -1; sec = -1; deci = -1;
15224             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15225                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15226                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15227                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15228                 return text;
15229             }
15230
15231             if( score_lo < 0 || score_lo >= 100 ) {
15232                 return text;
15233             }
15234
15235             if(sec >= 0) time = 600*time + 10*sec; else
15236             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15237
15238             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15239
15240             /* [HGM] PV time: now locate end of PV info */
15241             while( *++sep >= '0' && *sep <= '9'); // strip depth
15242             if(time >= 0)
15243             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15244             if(sec >= 0)
15245             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15246             if(deci >= 0)
15247             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15248             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15249         }
15250
15251         if( depth <= 0 ) {
15252             return text;
15253         }
15254
15255         if( time < 0 ) {
15256             time = -1;
15257         }
15258
15259         pvInfoList[index-1].depth = depth;
15260         pvInfoList[index-1].score = score;
15261         pvInfoList[index-1].time  = 10*time; // centi-sec
15262         if(*sep == '}') *sep = 0; else *--sep = '{';
15263         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15264     }
15265     return sep;
15266 }
15267
15268 void
15269 SendToProgram (char *message, ChessProgramState *cps)
15270 {
15271     int count, outCount, error;
15272     char buf[MSG_SIZ];
15273
15274     if (cps->pr == NoProc) return;
15275     Attention(cps);
15276
15277     if (appData.debugMode) {
15278         TimeMark now;
15279         GetTimeMark(&now);
15280         fprintf(debugFP, "%ld >%-6s: %s",
15281                 SubtractTimeMarks(&now, &programStartTime),
15282                 cps->which, message);
15283         if(serverFP)
15284             fprintf(serverFP, "%ld >%-6s: %s",
15285                 SubtractTimeMarks(&now, &programStartTime),
15286                 cps->which, message), fflush(serverFP);
15287     }
15288
15289     count = strlen(message);
15290     outCount = OutputToProcess(cps->pr, message, count, &error);
15291     if (outCount < count && !exiting
15292                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15293       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15294       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15295         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15296             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15297                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15298                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15299                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15300             } else {
15301                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15302                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15303                 gameInfo.result = res;
15304             }
15305             gameInfo.resultDetails = StrSave(buf);
15306         }
15307         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15308         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15309     }
15310 }
15311
15312 void
15313 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15314 {
15315     char *end_str;
15316     char buf[MSG_SIZ];
15317     ChessProgramState *cps = (ChessProgramState *)closure;
15318
15319     if (isr != cps->isr) return; /* Killed intentionally */
15320     if (count <= 0) {
15321         if (count == 0) {
15322             RemoveInputSource(cps->isr);
15323             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15324                     _(cps->which), cps->program);
15325             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15326             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15327                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15328                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15329                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15330                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15331                 } else {
15332                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15333                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15334                     gameInfo.result = res;
15335                 }
15336                 gameInfo.resultDetails = StrSave(buf);
15337             }
15338             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15339             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15340         } else {
15341             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15342                     _(cps->which), cps->program);
15343             RemoveInputSource(cps->isr);
15344
15345             /* [AS] Program is misbehaving badly... kill it */
15346             if( count == -2 ) {
15347                 DestroyChildProcess( cps->pr, 9 );
15348                 cps->pr = NoProc;
15349             }
15350
15351             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15352         }
15353         return;
15354     }
15355
15356     if ((end_str = strchr(message, '\r')) != NULL)
15357       *end_str = NULLCHAR;
15358     if ((end_str = strchr(message, '\n')) != NULL)
15359       *end_str = NULLCHAR;
15360
15361     if (appData.debugMode) {
15362         TimeMark now; int print = 1;
15363         char *quote = ""; char c; int i;
15364
15365         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15366                 char start = message[0];
15367                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15368                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15369                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15370                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15371                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15372                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15373                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15374                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15375                    sscanf(message, "hint: %c", &c)!=1 && 
15376                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15377                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15378                     print = (appData.engineComments >= 2);
15379                 }
15380                 message[0] = start; // restore original message
15381         }
15382         if(print) {
15383                 GetTimeMark(&now);
15384                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15385                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15386                         quote,
15387                         message);
15388                 if(serverFP)
15389                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15390                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15391                         quote,
15392                         message), fflush(serverFP);
15393         }
15394     }
15395
15396     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15397     if (appData.icsEngineAnalyze) {
15398         if (strstr(message, "whisper") != NULL ||
15399              strstr(message, "kibitz") != NULL ||
15400             strstr(message, "tellics") != NULL) return;
15401     }
15402
15403     HandleMachineMove(message, cps);
15404 }
15405
15406
15407 void
15408 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15409 {
15410     char buf[MSG_SIZ];
15411     int seconds;
15412
15413     if( timeControl_2 > 0 ) {
15414         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15415             tc = timeControl_2;
15416         }
15417     }
15418     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15419     inc /= cps->timeOdds;
15420     st  /= cps->timeOdds;
15421
15422     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15423
15424     if (st > 0) {
15425       /* Set exact time per move, normally using st command */
15426       if (cps->stKludge) {
15427         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15428         seconds = st % 60;
15429         if (seconds == 0) {
15430           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15431         } else {
15432           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15433         }
15434       } else {
15435         snprintf(buf, MSG_SIZ, "st %d\n", st);
15436       }
15437     } else {
15438       /* Set conventional or incremental time control, using level command */
15439       if (seconds == 0) {
15440         /* Note old gnuchess bug -- minutes:seconds used to not work.
15441            Fixed in later versions, but still avoid :seconds
15442            when seconds is 0. */
15443         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15444       } else {
15445         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15446                  seconds, inc/1000.);
15447       }
15448     }
15449     SendToProgram(buf, cps);
15450
15451     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15452     /* Orthogonally, limit search to given depth */
15453     if (sd > 0) {
15454       if (cps->sdKludge) {
15455         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15456       } else {
15457         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15458       }
15459       SendToProgram(buf, cps);
15460     }
15461
15462     if(cps->nps >= 0) { /* [HGM] nps */
15463         if(cps->supportsNPS == FALSE)
15464           cps->nps = -1; // don't use if engine explicitly says not supported!
15465         else {
15466           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15467           SendToProgram(buf, cps);
15468         }
15469     }
15470 }
15471
15472 ChessProgramState *
15473 WhitePlayer ()
15474 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15475 {
15476     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15477        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15478         return &second;
15479     return &first;
15480 }
15481
15482 void
15483 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15484 {
15485     char message[MSG_SIZ];
15486     long time, otime;
15487
15488     /* Note: this routine must be called when the clocks are stopped
15489        or when they have *just* been set or switched; otherwise
15490        it will be off by the time since the current tick started.
15491     */
15492     if (machineWhite) {
15493         time = whiteTimeRemaining / 10;
15494         otime = blackTimeRemaining / 10;
15495     } else {
15496         time = blackTimeRemaining / 10;
15497         otime = whiteTimeRemaining / 10;
15498     }
15499     /* [HGM] translate opponent's time by time-odds factor */
15500     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15501
15502     if (time <= 0) time = 1;
15503     if (otime <= 0) otime = 1;
15504
15505     snprintf(message, MSG_SIZ, "time %ld\n", time);
15506     SendToProgram(message, cps);
15507
15508     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15509     SendToProgram(message, cps);
15510 }
15511
15512 int
15513 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15514 {
15515   char buf[MSG_SIZ];
15516   int len = strlen(name);
15517   int val;
15518
15519   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15520     (*p) += len + 1;
15521     sscanf(*p, "%d", &val);
15522     *loc = (val != 0);
15523     while (**p && **p != ' ')
15524       (*p)++;
15525     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15526     SendToProgram(buf, cps);
15527     return TRUE;
15528   }
15529   return FALSE;
15530 }
15531
15532 int
15533 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15534 {
15535   char buf[MSG_SIZ];
15536   int len = strlen(name);
15537   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15538     (*p) += len + 1;
15539     sscanf(*p, "%d", loc);
15540     while (**p && **p != ' ') (*p)++;
15541     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15542     SendToProgram(buf, cps);
15543     return TRUE;
15544   }
15545   return FALSE;
15546 }
15547
15548 int
15549 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15550 {
15551   char buf[MSG_SIZ];
15552   int len = strlen(name);
15553   if (strncmp((*p), name, len) == 0
15554       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15555     (*p) += len + 2;
15556     sscanf(*p, "%[^\"]", loc);
15557     while (**p && **p != '\"') (*p)++;
15558     if (**p == '\"') (*p)++;
15559     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15560     SendToProgram(buf, cps);
15561     return TRUE;
15562   }
15563   return FALSE;
15564 }
15565
15566 int
15567 ParseOption (Option *opt, ChessProgramState *cps)
15568 // [HGM] options: process the string that defines an engine option, and determine
15569 // name, type, default value, and allowed value range
15570 {
15571         char *p, *q, buf[MSG_SIZ];
15572         int n, min = (-1)<<31, max = 1<<31, def;
15573
15574         if(p = strstr(opt->name, " -spin ")) {
15575             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15576             if(max < min) max = min; // enforce consistency
15577             if(def < min) def = min;
15578             if(def > max) def = max;
15579             opt->value = def;
15580             opt->min = min;
15581             opt->max = max;
15582             opt->type = Spin;
15583         } else if((p = strstr(opt->name, " -slider "))) {
15584             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15585             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15586             if(max < min) max = min; // enforce consistency
15587             if(def < min) def = min;
15588             if(def > max) def = max;
15589             opt->value = def;
15590             opt->min = min;
15591             opt->max = max;
15592             opt->type = Spin; // Slider;
15593         } else if((p = strstr(opt->name, " -string "))) {
15594             opt->textValue = p+9;
15595             opt->type = TextBox;
15596         } else if((p = strstr(opt->name, " -file "))) {
15597             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15598             opt->textValue = p+7;
15599             opt->type = FileName; // FileName;
15600         } else if((p = strstr(opt->name, " -path "))) {
15601             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15602             opt->textValue = p+7;
15603             opt->type = PathName; // PathName;
15604         } else if(p = strstr(opt->name, " -check ")) {
15605             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15606             opt->value = (def != 0);
15607             opt->type = CheckBox;
15608         } else if(p = strstr(opt->name, " -combo ")) {
15609             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15610             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15611             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15612             opt->value = n = 0;
15613             while(q = StrStr(q, " /// ")) {
15614                 n++; *q = 0;    // count choices, and null-terminate each of them
15615                 q += 5;
15616                 if(*q == '*') { // remember default, which is marked with * prefix
15617                     q++;
15618                     opt->value = n;
15619                 }
15620                 cps->comboList[cps->comboCnt++] = q;
15621             }
15622             cps->comboList[cps->comboCnt++] = NULL;
15623             opt->max = n + 1;
15624             opt->type = ComboBox;
15625         } else if(p = strstr(opt->name, " -button")) {
15626             opt->type = Button;
15627         } else if(p = strstr(opt->name, " -save")) {
15628             opt->type = SaveButton;
15629         } else return FALSE;
15630         *p = 0; // terminate option name
15631         // now look if the command-line options define a setting for this engine option.
15632         if(cps->optionSettings && cps->optionSettings[0])
15633             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15634         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15635           snprintf(buf, MSG_SIZ, "option %s", p);
15636                 if(p = strstr(buf, ",")) *p = 0;
15637                 if(q = strchr(buf, '=')) switch(opt->type) {
15638                     case ComboBox:
15639                         for(n=0; n<opt->max; n++)
15640                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15641                         break;
15642                     case TextBox:
15643                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15644                         break;
15645                     case Spin:
15646                     case CheckBox:
15647                         opt->value = atoi(q+1);
15648                     default:
15649                         break;
15650                 }
15651                 strcat(buf, "\n");
15652                 SendToProgram(buf, cps);
15653         }
15654         return TRUE;
15655 }
15656
15657 void
15658 FeatureDone (ChessProgramState *cps, int val)
15659 {
15660   DelayedEventCallback cb = GetDelayedEvent();
15661   if ((cb == InitBackEnd3 && cps == &first) ||
15662       (cb == SettingsMenuIfReady && cps == &second) ||
15663       (cb == LoadEngine) ||
15664       (cb == TwoMachinesEventIfReady)) {
15665     CancelDelayedEvent();
15666     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15667   }
15668   cps->initDone = val;
15669 }
15670
15671 /* Parse feature command from engine */
15672 void
15673 ParseFeatures (char *args, ChessProgramState *cps)
15674 {
15675   char *p = args;
15676   char *q;
15677   int val;
15678   char buf[MSG_SIZ];
15679
15680   for (;;) {
15681     while (*p == ' ') p++;
15682     if (*p == NULLCHAR) return;
15683
15684     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15685     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15686     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15687     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15688     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15689     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15690     if (BoolFeature(&p, "reuse", &val, cps)) {
15691       /* Engine can disable reuse, but can't enable it if user said no */
15692       if (!val) cps->reuse = FALSE;
15693       continue;
15694     }
15695     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15696     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15697       if (gameMode == TwoMachinesPlay) {
15698         DisplayTwoMachinesTitle();
15699       } else {
15700         DisplayTitle("");
15701       }
15702       continue;
15703     }
15704     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15705     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15706     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15707     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15708     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15709     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15710     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15711     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15712     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15713     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15714     if (IntFeature(&p, "done", &val, cps)) {
15715       FeatureDone(cps, val);
15716       continue;
15717     }
15718     /* Added by Tord: */
15719     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15720     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15721     /* End of additions by Tord */
15722
15723     /* [HGM] added features: */
15724     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15725     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15726     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15727     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15728     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15729     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15730     if (StringFeature(&p, "option", buf, cps)) {
15731         FREE(cps->option[cps->nrOptions].name);
15732         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15733         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15734         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15735           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15736             SendToProgram(buf, cps);
15737             continue;
15738         }
15739         if(cps->nrOptions >= MAX_OPTIONS) {
15740             cps->nrOptions--;
15741             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15742             DisplayError(buf, 0);
15743         }
15744         continue;
15745     }
15746     /* End of additions by HGM */
15747
15748     /* unknown feature: complain and skip */
15749     q = p;
15750     while (*q && *q != '=') q++;
15751     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15752     SendToProgram(buf, cps);
15753     p = q;
15754     if (*p == '=') {
15755       p++;
15756       if (*p == '\"') {
15757         p++;
15758         while (*p && *p != '\"') p++;
15759         if (*p == '\"') p++;
15760       } else {
15761         while (*p && *p != ' ') p++;
15762       }
15763     }
15764   }
15765
15766 }
15767
15768 void
15769 PeriodicUpdatesEvent (int newState)
15770 {
15771     if (newState == appData.periodicUpdates)
15772       return;
15773
15774     appData.periodicUpdates=newState;
15775
15776     /* Display type changes, so update it now */
15777 //    DisplayAnalysis();
15778
15779     /* Get the ball rolling again... */
15780     if (newState) {
15781         AnalysisPeriodicEvent(1);
15782         StartAnalysisClock();
15783     }
15784 }
15785
15786 void
15787 PonderNextMoveEvent (int newState)
15788 {
15789     if (newState == appData.ponderNextMove) return;
15790     if (gameMode == EditPosition) EditPositionDone(TRUE);
15791     if (newState) {
15792         SendToProgram("hard\n", &first);
15793         if (gameMode == TwoMachinesPlay) {
15794             SendToProgram("hard\n", &second);
15795         }
15796     } else {
15797         SendToProgram("easy\n", &first);
15798         thinkOutput[0] = NULLCHAR;
15799         if (gameMode == TwoMachinesPlay) {
15800             SendToProgram("easy\n", &second);
15801         }
15802     }
15803     appData.ponderNextMove = newState;
15804 }
15805
15806 void
15807 NewSettingEvent (int option, int *feature, char *command, int value)
15808 {
15809     char buf[MSG_SIZ];
15810
15811     if (gameMode == EditPosition) EditPositionDone(TRUE);
15812     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15813     if(feature == NULL || *feature) SendToProgram(buf, &first);
15814     if (gameMode == TwoMachinesPlay) {
15815         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15816     }
15817 }
15818
15819 void
15820 ShowThinkingEvent ()
15821 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15822 {
15823     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15824     int newState = appData.showThinking
15825         // [HGM] thinking: other features now need thinking output as well
15826         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15827
15828     if (oldState == newState) return;
15829     oldState = newState;
15830     if (gameMode == EditPosition) EditPositionDone(TRUE);
15831     if (oldState) {
15832         SendToProgram("post\n", &first);
15833         if (gameMode == TwoMachinesPlay) {
15834             SendToProgram("post\n", &second);
15835         }
15836     } else {
15837         SendToProgram("nopost\n", &first);
15838         thinkOutput[0] = NULLCHAR;
15839         if (gameMode == TwoMachinesPlay) {
15840             SendToProgram("nopost\n", &second);
15841         }
15842     }
15843 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15844 }
15845
15846 void
15847 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15848 {
15849   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15850   if (pr == NoProc) return;
15851   AskQuestion(title, question, replyPrefix, pr);
15852 }
15853
15854 void
15855 TypeInEvent (char firstChar)
15856 {
15857     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15858         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15859         gameMode == AnalyzeMode || gameMode == EditGame || 
15860         gameMode == EditPosition || gameMode == IcsExamining ||
15861         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15862         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15863                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15864                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15865         gameMode == Training) PopUpMoveDialog(firstChar);
15866 }
15867
15868 void
15869 TypeInDoneEvent (char *move)
15870 {
15871         Board board;
15872         int n, fromX, fromY, toX, toY;
15873         char promoChar;
15874         ChessMove moveType;
15875
15876         // [HGM] FENedit
15877         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15878                 EditPositionPasteFEN(move);
15879                 return;
15880         }
15881         // [HGM] movenum: allow move number to be typed in any mode
15882         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15883           ToNrEvent(2*n-1);
15884           return;
15885         }
15886         // undocumented kludge: allow command-line option to be typed in!
15887         // (potentially fatal, and does not implement the effect of the option.)
15888         // should only be used for options that are values on which future decisions will be made,
15889         // and definitely not on options that would be used during initialization.
15890         if(strstr(move, "!!! -") == move) {
15891             ParseArgsFromString(move+4);
15892             return;
15893         }
15894
15895       if (gameMode != EditGame && currentMove != forwardMostMove && 
15896         gameMode != Training) {
15897         DisplayMoveError(_("Displayed move is not current"));
15898       } else {
15899         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15900           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15901         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15902         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15903           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15904           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15905         } else {
15906           DisplayMoveError(_("Could not parse move"));
15907         }
15908       }
15909 }
15910
15911 void
15912 DisplayMove (int moveNumber)
15913 {
15914     char message[MSG_SIZ];
15915     char res[MSG_SIZ];
15916     char cpThinkOutput[MSG_SIZ];
15917
15918     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15919
15920     if (moveNumber == forwardMostMove - 1 ||
15921         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15922
15923         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15924
15925         if (strchr(cpThinkOutput, '\n')) {
15926             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15927         }
15928     } else {
15929         *cpThinkOutput = NULLCHAR;
15930     }
15931
15932     /* [AS] Hide thinking from human user */
15933     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15934         *cpThinkOutput = NULLCHAR;
15935         if( thinkOutput[0] != NULLCHAR ) {
15936             int i;
15937
15938             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15939                 cpThinkOutput[i] = '.';
15940             }
15941             cpThinkOutput[i] = NULLCHAR;
15942             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15943         }
15944     }
15945
15946     if (moveNumber == forwardMostMove - 1 &&
15947         gameInfo.resultDetails != NULL) {
15948         if (gameInfo.resultDetails[0] == NULLCHAR) {
15949           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15950         } else {
15951           snprintf(res, MSG_SIZ, " {%s} %s",
15952                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15953         }
15954     } else {
15955         res[0] = NULLCHAR;
15956     }
15957
15958     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15959         DisplayMessage(res, cpThinkOutput);
15960     } else {
15961       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15962                 WhiteOnMove(moveNumber) ? " " : ".. ",
15963                 parseList[moveNumber], res);
15964         DisplayMessage(message, cpThinkOutput);
15965     }
15966 }
15967
15968 void
15969 DisplayComment (int moveNumber, char *text)
15970 {
15971     char title[MSG_SIZ];
15972
15973     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15974       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15975     } else {
15976       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15977               WhiteOnMove(moveNumber) ? " " : ".. ",
15978               parseList[moveNumber]);
15979     }
15980     if (text != NULL && (appData.autoDisplayComment || commentUp))
15981         CommentPopUp(title, text);
15982 }
15983
15984 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15985  * might be busy thinking or pondering.  It can be omitted if your
15986  * gnuchess is configured to stop thinking immediately on any user
15987  * input.  However, that gnuchess feature depends on the FIONREAD
15988  * ioctl, which does not work properly on some flavors of Unix.
15989  */
15990 void
15991 Attention (ChessProgramState *cps)
15992 {
15993 #if ATTENTION
15994     if (!cps->useSigint) return;
15995     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15996     switch (gameMode) {
15997       case MachinePlaysWhite:
15998       case MachinePlaysBlack:
15999       case TwoMachinesPlay:
16000       case IcsPlayingWhite:
16001       case IcsPlayingBlack:
16002       case AnalyzeMode:
16003       case AnalyzeFile:
16004         /* Skip if we know it isn't thinking */
16005         if (!cps->maybeThinking) return;
16006         if (appData.debugMode)
16007           fprintf(debugFP, "Interrupting %s\n", cps->which);
16008         InterruptChildProcess(cps->pr);
16009         cps->maybeThinking = FALSE;
16010         break;
16011       default:
16012         break;
16013     }
16014 #endif /*ATTENTION*/
16015 }
16016
16017 int
16018 CheckFlags ()
16019 {
16020     if (whiteTimeRemaining <= 0) {
16021         if (!whiteFlag) {
16022             whiteFlag = TRUE;
16023             if (appData.icsActive) {
16024                 if (appData.autoCallFlag &&
16025                     gameMode == IcsPlayingBlack && !blackFlag) {
16026                   SendToICS(ics_prefix);
16027                   SendToICS("flag\n");
16028                 }
16029             } else {
16030                 if (blackFlag) {
16031                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16032                 } else {
16033                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16034                     if (appData.autoCallFlag) {
16035                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16036                         return TRUE;
16037                     }
16038                 }
16039             }
16040         }
16041     }
16042     if (blackTimeRemaining <= 0) {
16043         if (!blackFlag) {
16044             blackFlag = TRUE;
16045             if (appData.icsActive) {
16046                 if (appData.autoCallFlag &&
16047                     gameMode == IcsPlayingWhite && !whiteFlag) {
16048                   SendToICS(ics_prefix);
16049                   SendToICS("flag\n");
16050                 }
16051             } else {
16052                 if (whiteFlag) {
16053                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16054                 } else {
16055                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16056                     if (appData.autoCallFlag) {
16057                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16058                         return TRUE;
16059                     }
16060                 }
16061             }
16062         }
16063     }
16064     return FALSE;
16065 }
16066
16067 void
16068 CheckTimeControl ()
16069 {
16070     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16071         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16072
16073     /*
16074      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16075      */
16076     if ( !WhiteOnMove(forwardMostMove) ) {
16077         /* White made time control */
16078         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16079         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16080         /* [HGM] time odds: correct new time quota for time odds! */
16081                                             / WhitePlayer()->timeOdds;
16082         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16083     } else {
16084         lastBlack -= blackTimeRemaining;
16085         /* Black made time control */
16086         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16087                                             / WhitePlayer()->other->timeOdds;
16088         lastWhite = whiteTimeRemaining;
16089     }
16090 }
16091
16092 void
16093 DisplayBothClocks ()
16094 {
16095     int wom = gameMode == EditPosition ?
16096       !blackPlaysFirst : WhiteOnMove(currentMove);
16097     DisplayWhiteClock(whiteTimeRemaining, wom);
16098     DisplayBlackClock(blackTimeRemaining, !wom);
16099 }
16100
16101
16102 /* Timekeeping seems to be a portability nightmare.  I think everyone
16103    has ftime(), but I'm really not sure, so I'm including some ifdefs
16104    to use other calls if you don't.  Clocks will be less accurate if
16105    you have neither ftime nor gettimeofday.
16106 */
16107
16108 /* VS 2008 requires the #include outside of the function */
16109 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16110 #include <sys/timeb.h>
16111 #endif
16112
16113 /* Get the current time as a TimeMark */
16114 void
16115 GetTimeMark (TimeMark *tm)
16116 {
16117 #if HAVE_GETTIMEOFDAY
16118
16119     struct timeval timeVal;
16120     struct timezone timeZone;
16121
16122     gettimeofday(&timeVal, &timeZone);
16123     tm->sec = (long) timeVal.tv_sec;
16124     tm->ms = (int) (timeVal.tv_usec / 1000L);
16125
16126 #else /*!HAVE_GETTIMEOFDAY*/
16127 #if HAVE_FTIME
16128
16129 // include <sys/timeb.h> / moved to just above start of function
16130     struct timeb timeB;
16131
16132     ftime(&timeB);
16133     tm->sec = (long) timeB.time;
16134     tm->ms = (int) timeB.millitm;
16135
16136 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16137     tm->sec = (long) time(NULL);
16138     tm->ms = 0;
16139 #endif
16140 #endif
16141 }
16142
16143 /* Return the difference in milliseconds between two
16144    time marks.  We assume the difference will fit in a long!
16145 */
16146 long
16147 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16148 {
16149     return 1000L*(tm2->sec - tm1->sec) +
16150            (long) (tm2->ms - tm1->ms);
16151 }
16152
16153
16154 /*
16155  * Code to manage the game clocks.
16156  *
16157  * In tournament play, black starts the clock and then white makes a move.
16158  * We give the human user a slight advantage if he is playing white---the
16159  * clocks don't run until he makes his first move, so it takes zero time.
16160  * Also, we don't account for network lag, so we could get out of sync
16161  * with GNU Chess's clock -- but then, referees are always right.
16162  */
16163
16164 static TimeMark tickStartTM;
16165 static long intendedTickLength;
16166
16167 long
16168 NextTickLength (long timeRemaining)
16169 {
16170     long nominalTickLength, nextTickLength;
16171
16172     if (timeRemaining > 0L && timeRemaining <= 10000L)
16173       nominalTickLength = 100L;
16174     else
16175       nominalTickLength = 1000L;
16176     nextTickLength = timeRemaining % nominalTickLength;
16177     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16178
16179     return nextTickLength;
16180 }
16181
16182 /* Adjust clock one minute up or down */
16183 void
16184 AdjustClock (Boolean which, int dir)
16185 {
16186     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16187     if(which) blackTimeRemaining += 60000*dir;
16188     else      whiteTimeRemaining += 60000*dir;
16189     DisplayBothClocks();
16190     adjustedClock = TRUE;
16191 }
16192
16193 /* Stop clocks and reset to a fresh time control */
16194 void
16195 ResetClocks ()
16196 {
16197     (void) StopClockTimer();
16198     if (appData.icsActive) {
16199         whiteTimeRemaining = blackTimeRemaining = 0;
16200     } else if (searchTime) {
16201         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16202         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16203     } else { /* [HGM] correct new time quote for time odds */
16204         whiteTC = blackTC = fullTimeControlString;
16205         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16206         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16207     }
16208     if (whiteFlag || blackFlag) {
16209         DisplayTitle("");
16210         whiteFlag = blackFlag = FALSE;
16211     }
16212     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16213     DisplayBothClocks();
16214     adjustedClock = FALSE;
16215 }
16216
16217 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16218
16219 /* Decrement running clock by amount of time that has passed */
16220 void
16221 DecrementClocks ()
16222 {
16223     long timeRemaining;
16224     long lastTickLength, fudge;
16225     TimeMark now;
16226
16227     if (!appData.clockMode) return;
16228     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16229
16230     GetTimeMark(&now);
16231
16232     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16233
16234     /* Fudge if we woke up a little too soon */
16235     fudge = intendedTickLength - lastTickLength;
16236     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16237
16238     if (WhiteOnMove(forwardMostMove)) {
16239         if(whiteNPS >= 0) lastTickLength = 0;
16240         timeRemaining = whiteTimeRemaining -= lastTickLength;
16241         if(timeRemaining < 0 && !appData.icsActive) {
16242             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16243             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16244                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16245                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16246             }
16247         }
16248         DisplayWhiteClock(whiteTimeRemaining - fudge,
16249                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16250     } else {
16251         if(blackNPS >= 0) lastTickLength = 0;
16252         timeRemaining = blackTimeRemaining -= lastTickLength;
16253         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16254             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16255             if(suddenDeath) {
16256                 blackStartMove = forwardMostMove;
16257                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16258             }
16259         }
16260         DisplayBlackClock(blackTimeRemaining - fudge,
16261                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16262     }
16263     if (CheckFlags()) return;
16264
16265     if(twoBoards) { // count down secondary board's clocks as well
16266         activePartnerTime -= lastTickLength;
16267         partnerUp = 1;
16268         if(activePartner == 'W')
16269             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16270         else
16271             DisplayBlackClock(activePartnerTime, TRUE);
16272         partnerUp = 0;
16273     }
16274
16275     tickStartTM = now;
16276     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16277     StartClockTimer(intendedTickLength);
16278
16279     /* if the time remaining has fallen below the alarm threshold, sound the
16280      * alarm. if the alarm has sounded and (due to a takeback or time control
16281      * with increment) the time remaining has increased to a level above the
16282      * threshold, reset the alarm so it can sound again.
16283      */
16284
16285     if (appData.icsActive && appData.icsAlarm) {
16286
16287         /* make sure we are dealing with the user's clock */
16288         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16289                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16290            )) return;
16291
16292         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16293             alarmSounded = FALSE;
16294         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16295             PlayAlarmSound();
16296             alarmSounded = TRUE;
16297         }
16298     }
16299 }
16300
16301
16302 /* A player has just moved, so stop the previously running
16303    clock and (if in clock mode) start the other one.
16304    We redisplay both clocks in case we're in ICS mode, because
16305    ICS gives us an update to both clocks after every move.
16306    Note that this routine is called *after* forwardMostMove
16307    is updated, so the last fractional tick must be subtracted
16308    from the color that is *not* on move now.
16309 */
16310 void
16311 SwitchClocks (int newMoveNr)
16312 {
16313     long lastTickLength;
16314     TimeMark now;
16315     int flagged = FALSE;
16316
16317     GetTimeMark(&now);
16318
16319     if (StopClockTimer() && appData.clockMode) {
16320         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16321         if (!WhiteOnMove(forwardMostMove)) {
16322             if(blackNPS >= 0) lastTickLength = 0;
16323             blackTimeRemaining -= lastTickLength;
16324            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16325 //         if(pvInfoList[forwardMostMove].time == -1)
16326                  pvInfoList[forwardMostMove].time =               // use GUI time
16327                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16328         } else {
16329            if(whiteNPS >= 0) lastTickLength = 0;
16330            whiteTimeRemaining -= lastTickLength;
16331            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16332 //         if(pvInfoList[forwardMostMove].time == -1)
16333                  pvInfoList[forwardMostMove].time =
16334                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16335         }
16336         flagged = CheckFlags();
16337     }
16338     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16339     CheckTimeControl();
16340
16341     if (flagged || !appData.clockMode) return;
16342
16343     switch (gameMode) {
16344       case MachinePlaysBlack:
16345       case MachinePlaysWhite:
16346       case BeginningOfGame:
16347         if (pausing) return;
16348         break;
16349
16350       case EditGame:
16351       case PlayFromGameFile:
16352       case IcsExamining:
16353         return;
16354
16355       default:
16356         break;
16357     }
16358
16359     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16360         if(WhiteOnMove(forwardMostMove))
16361              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16362         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16363     }
16364
16365     tickStartTM = now;
16366     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16367       whiteTimeRemaining : blackTimeRemaining);
16368     StartClockTimer(intendedTickLength);
16369 }
16370
16371
16372 /* Stop both clocks */
16373 void
16374 StopClocks ()
16375 {
16376     long lastTickLength;
16377     TimeMark now;
16378
16379     if (!StopClockTimer()) return;
16380     if (!appData.clockMode) return;
16381
16382     GetTimeMark(&now);
16383
16384     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16385     if (WhiteOnMove(forwardMostMove)) {
16386         if(whiteNPS >= 0) lastTickLength = 0;
16387         whiteTimeRemaining -= lastTickLength;
16388         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16389     } else {
16390         if(blackNPS >= 0) lastTickLength = 0;
16391         blackTimeRemaining -= lastTickLength;
16392         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16393     }
16394     CheckFlags();
16395 }
16396
16397 /* Start clock of player on move.  Time may have been reset, so
16398    if clock is already running, stop and restart it. */
16399 void
16400 StartClocks ()
16401 {
16402     (void) StopClockTimer(); /* in case it was running already */
16403     DisplayBothClocks();
16404     if (CheckFlags()) return;
16405
16406     if (!appData.clockMode) return;
16407     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16408
16409     GetTimeMark(&tickStartTM);
16410     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16411       whiteTimeRemaining : blackTimeRemaining);
16412
16413    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16414     whiteNPS = blackNPS = -1;
16415     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16416        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16417         whiteNPS = first.nps;
16418     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16419        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16420         blackNPS = first.nps;
16421     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16422         whiteNPS = second.nps;
16423     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16424         blackNPS = second.nps;
16425     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16426
16427     StartClockTimer(intendedTickLength);
16428 }
16429
16430 char *
16431 TimeString (long ms)
16432 {
16433     long second, minute, hour, day;
16434     char *sign = "";
16435     static char buf[32];
16436
16437     if (ms > 0 && ms <= 9900) {
16438       /* convert milliseconds to tenths, rounding up */
16439       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16440
16441       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16442       return buf;
16443     }
16444
16445     /* convert milliseconds to seconds, rounding up */
16446     /* use floating point to avoid strangeness of integer division
16447        with negative dividends on many machines */
16448     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16449
16450     if (second < 0) {
16451         sign = "-";
16452         second = -second;
16453     }
16454
16455     day = second / (60 * 60 * 24);
16456     second = second % (60 * 60 * 24);
16457     hour = second / (60 * 60);
16458     second = second % (60 * 60);
16459     minute = second / 60;
16460     second = second % 60;
16461
16462     if (day > 0)
16463       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16464               sign, day, hour, minute, second);
16465     else if (hour > 0)
16466       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16467     else
16468       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16469
16470     return buf;
16471 }
16472
16473
16474 /*
16475  * This is necessary because some C libraries aren't ANSI C compliant yet.
16476  */
16477 char *
16478 StrStr (char *string, char *match)
16479 {
16480     int i, length;
16481
16482     length = strlen(match);
16483
16484     for (i = strlen(string) - length; i >= 0; i--, string++)
16485       if (!strncmp(match, string, length))
16486         return string;
16487
16488     return NULL;
16489 }
16490
16491 char *
16492 StrCaseStr (char *string, char *match)
16493 {
16494     int i, j, length;
16495
16496     length = strlen(match);
16497
16498     for (i = strlen(string) - length; i >= 0; i--, string++) {
16499         for (j = 0; j < length; j++) {
16500             if (ToLower(match[j]) != ToLower(string[j]))
16501               break;
16502         }
16503         if (j == length) return string;
16504     }
16505
16506     return NULL;
16507 }
16508
16509 #ifndef _amigados
16510 int
16511 StrCaseCmp (char *s1, char *s2)
16512 {
16513     char c1, c2;
16514
16515     for (;;) {
16516         c1 = ToLower(*s1++);
16517         c2 = ToLower(*s2++);
16518         if (c1 > c2) return 1;
16519         if (c1 < c2) return -1;
16520         if (c1 == NULLCHAR) return 0;
16521     }
16522 }
16523
16524
16525 int
16526 ToLower (int c)
16527 {
16528     return isupper(c) ? tolower(c) : c;
16529 }
16530
16531
16532 int
16533 ToUpper (int c)
16534 {
16535     return islower(c) ? toupper(c) : c;
16536 }
16537 #endif /* !_amigados    */
16538
16539 char *
16540 StrSave (char *s)
16541 {
16542   char *ret;
16543
16544   if ((ret = (char *) malloc(strlen(s) + 1)))
16545     {
16546       safeStrCpy(ret, s, strlen(s)+1);
16547     }
16548   return ret;
16549 }
16550
16551 char *
16552 StrSavePtr (char *s, char **savePtr)
16553 {
16554     if (*savePtr) {
16555         free(*savePtr);
16556     }
16557     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16558       safeStrCpy(*savePtr, s, strlen(s)+1);
16559     }
16560     return(*savePtr);
16561 }
16562
16563 char *
16564 PGNDate ()
16565 {
16566     time_t clock;
16567     struct tm *tm;
16568     char buf[MSG_SIZ];
16569
16570     clock = time((time_t *)NULL);
16571     tm = localtime(&clock);
16572     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16573             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16574     return StrSave(buf);
16575 }
16576
16577
16578 char *
16579 PositionToFEN (int move, char *overrideCastling)
16580 {
16581     int i, j, fromX, fromY, toX, toY;
16582     int whiteToPlay;
16583     char buf[MSG_SIZ];
16584     char *p, *q;
16585     int emptycount;
16586     ChessSquare piece;
16587
16588     whiteToPlay = (gameMode == EditPosition) ?
16589       !blackPlaysFirst : (move % 2 == 0);
16590     p = buf;
16591
16592     /* Piece placement data */
16593     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16594         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16595         emptycount = 0;
16596         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16597             if (boards[move][i][j] == EmptySquare) {
16598                 emptycount++;
16599             } else { ChessSquare piece = boards[move][i][j];
16600                 if (emptycount > 0) {
16601                     if(emptycount<10) /* [HGM] can be >= 10 */
16602                         *p++ = '0' + emptycount;
16603                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16604                     emptycount = 0;
16605                 }
16606                 if(PieceToChar(piece) == '+') {
16607                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16608                     *p++ = '+';
16609                     piece = (ChessSquare)(DEMOTED piece);
16610                 }
16611                 *p++ = PieceToChar(piece);
16612                 if(p[-1] == '~') {
16613                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16614                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16615                     *p++ = '~';
16616                 }
16617             }
16618         }
16619         if (emptycount > 0) {
16620             if(emptycount<10) /* [HGM] can be >= 10 */
16621                 *p++ = '0' + emptycount;
16622             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16623             emptycount = 0;
16624         }
16625         *p++ = '/';
16626     }
16627     *(p - 1) = ' ';
16628
16629     /* [HGM] print Crazyhouse or Shogi holdings */
16630     if( gameInfo.holdingsWidth ) {
16631         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16632         q = p;
16633         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16634             piece = boards[move][i][BOARD_WIDTH-1];
16635             if( piece != EmptySquare )
16636               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16637                   *p++ = PieceToChar(piece);
16638         }
16639         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16640             piece = boards[move][BOARD_HEIGHT-i-1][0];
16641             if( piece != EmptySquare )
16642               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16643                   *p++ = PieceToChar(piece);
16644         }
16645
16646         if( q == p ) *p++ = '-';
16647         *p++ = ']';
16648         *p++ = ' ';
16649     }
16650
16651     /* Active color */
16652     *p++ = whiteToPlay ? 'w' : 'b';
16653     *p++ = ' ';
16654
16655   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16656     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16657   } else {
16658   if(nrCastlingRights) {
16659      q = p;
16660      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16661        /* [HGM] write directly from rights */
16662            if(boards[move][CASTLING][2] != NoRights &&
16663               boards[move][CASTLING][0] != NoRights   )
16664                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16665            if(boards[move][CASTLING][2] != NoRights &&
16666               boards[move][CASTLING][1] != NoRights   )
16667                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16668            if(boards[move][CASTLING][5] != NoRights &&
16669               boards[move][CASTLING][3] != NoRights   )
16670                 *p++ = boards[move][CASTLING][3] + AAA;
16671            if(boards[move][CASTLING][5] != NoRights &&
16672               boards[move][CASTLING][4] != NoRights   )
16673                 *p++ = boards[move][CASTLING][4] + AAA;
16674      } else {
16675
16676         /* [HGM] write true castling rights */
16677         if( nrCastlingRights == 6 ) {
16678             int q, k=0;
16679             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16680                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16681             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16682                  boards[move][CASTLING][2] != NoRights  );
16683             if(gameInfo.variant = VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16684                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16685                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16686                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16687                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16688             }
16689             if(q) *p++ = 'Q';
16690             k = 0;
16691             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16692                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16693             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16694                  boards[move][CASTLING][5] != NoRights  );
16695             if(gameInfo.variant = VariantSChess) {
16696                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16697                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16698                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16699                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16700             }
16701             if(q) *p++ = 'q';
16702         }
16703      }
16704      if (q == p) *p++ = '-'; /* No castling rights */
16705      *p++ = ' ';
16706   }
16707
16708   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16709      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16710     /* En passant target square */
16711     if (move > backwardMostMove) {
16712         fromX = moveList[move - 1][0] - AAA;
16713         fromY = moveList[move - 1][1] - ONE;
16714         toX = moveList[move - 1][2] - AAA;
16715         toY = moveList[move - 1][3] - ONE;
16716         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16717             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16718             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16719             fromX == toX) {
16720             /* 2-square pawn move just happened */
16721             *p++ = toX + AAA;
16722             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16723         } else {
16724             *p++ = '-';
16725         }
16726     } else if(move == backwardMostMove) {
16727         // [HGM] perhaps we should always do it like this, and forget the above?
16728         if((signed char)boards[move][EP_STATUS] >= 0) {
16729             *p++ = boards[move][EP_STATUS] + AAA;
16730             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16731         } else {
16732             *p++ = '-';
16733         }
16734     } else {
16735         *p++ = '-';
16736     }
16737     *p++ = ' ';
16738   }
16739   }
16740
16741     /* [HGM] find reversible plies */
16742     {   int i = 0, j=move;
16743
16744         if (appData.debugMode) { int k;
16745             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16746             for(k=backwardMostMove; k<=forwardMostMove; k++)
16747                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16748
16749         }
16750
16751         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16752         if( j == backwardMostMove ) i += initialRulePlies;
16753         sprintf(p, "%d ", i);
16754         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16755     }
16756     /* Fullmove number */
16757     sprintf(p, "%d", (move / 2) + 1);
16758
16759     return StrSave(buf);
16760 }
16761
16762 Boolean
16763 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16764 {
16765     int i, j;
16766     char *p, c;
16767     int emptycount, virgin[BOARD_FILES];
16768     ChessSquare piece;
16769
16770     p = fen;
16771
16772     /* [HGM] by default clear Crazyhouse holdings, if present */
16773     if(gameInfo.holdingsWidth) {
16774        for(i=0; i<BOARD_HEIGHT; i++) {
16775            board[i][0]             = EmptySquare; /* black holdings */
16776            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16777            board[i][1]             = (ChessSquare) 0; /* black counts */
16778            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16779        }
16780     }
16781
16782     /* Piece placement data */
16783     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16784         j = 0;
16785         for (;;) {
16786             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16787                 if (*p == '/') p++;
16788                 emptycount = gameInfo.boardWidth - j;
16789                 while (emptycount--)
16790                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16791                 break;
16792 #if(BOARD_FILES >= 10)
16793             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16794                 p++; emptycount=10;
16795                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16796                 while (emptycount--)
16797                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16798 #endif
16799             } else if (isdigit(*p)) {
16800                 emptycount = *p++ - '0';
16801                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16802                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16803                 while (emptycount--)
16804                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16805             } else if (*p == '+' || isalpha(*p)) {
16806                 if (j >= gameInfo.boardWidth) return FALSE;
16807                 if(*p=='+') {
16808                     piece = CharToPiece(*++p);
16809                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16810                     piece = (ChessSquare) (PROMOTED piece ); p++;
16811                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16812                 } else piece = CharToPiece(*p++);
16813
16814                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16815                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16816                     piece = (ChessSquare) (PROMOTED piece);
16817                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16818                     p++;
16819                 }
16820                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16821             } else {
16822                 return FALSE;
16823             }
16824         }
16825     }
16826     while (*p == '/' || *p == ' ') p++;
16827
16828     /* [HGM] look for Crazyhouse holdings here */
16829     while(*p==' ') p++;
16830     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16831         if(*p == '[') p++;
16832         if(*p == '-' ) p++; /* empty holdings */ else {
16833             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16834             /* if we would allow FEN reading to set board size, we would   */
16835             /* have to add holdings and shift the board read so far here   */
16836             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16837                 p++;
16838                 if((int) piece >= (int) BlackPawn ) {
16839                     i = (int)piece - (int)BlackPawn;
16840                     i = PieceToNumber((ChessSquare)i);
16841                     if( i >= gameInfo.holdingsSize ) return FALSE;
16842                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16843                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16844                 } else {
16845                     i = (int)piece - (int)WhitePawn;
16846                     i = PieceToNumber((ChessSquare)i);
16847                     if( i >= gameInfo.holdingsSize ) return FALSE;
16848                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16849                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16850                 }
16851             }
16852         }
16853         if(*p == ']') p++;
16854     }
16855
16856     while(*p == ' ') p++;
16857
16858     /* Active color */
16859     c = *p++;
16860     if(appData.colorNickNames) {
16861       if( c == appData.colorNickNames[0] ) c = 'w'; else
16862       if( c == appData.colorNickNames[1] ) c = 'b';
16863     }
16864     switch (c) {
16865       case 'w':
16866         *blackPlaysFirst = FALSE;
16867         break;
16868       case 'b':
16869         *blackPlaysFirst = TRUE;
16870         break;
16871       default:
16872         return FALSE;
16873     }
16874
16875     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16876     /* return the extra info in global variiables             */
16877
16878     /* set defaults in case FEN is incomplete */
16879     board[EP_STATUS] = EP_UNKNOWN;
16880     for(i=0; i<nrCastlingRights; i++ ) {
16881         board[CASTLING][i] =
16882             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16883     }   /* assume possible unless obviously impossible */
16884     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16885     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16886     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16887                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16888     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16889     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16890     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16891                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16892     FENrulePlies = 0;
16893
16894     while(*p==' ') p++;
16895     if(nrCastlingRights) {
16896       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
16897       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
16898           /* castling indicator present, so default becomes no castlings */
16899           for(i=0; i<nrCastlingRights; i++ ) {
16900                  board[CASTLING][i] = NoRights;
16901           }
16902       }
16903       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16904              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
16905              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16906              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16907         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
16908
16909         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16910             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16911             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16912         }
16913         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16914             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16915         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16916                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16917         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16918                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16919         switch(c) {
16920           case'K':
16921               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16922               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16923               board[CASTLING][2] = whiteKingFile;
16924               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
16925               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16926               break;
16927           case'Q':
16928               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16929               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16930               board[CASTLING][2] = whiteKingFile;
16931               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
16932               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16933               break;
16934           case'k':
16935               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16936               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16937               board[CASTLING][5] = blackKingFile;
16938               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
16939               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
16940               break;
16941           case'q':
16942               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16943               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16944               board[CASTLING][5] = blackKingFile;
16945               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
16946               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
16947           case '-':
16948               break;
16949           default: /* FRC castlings */
16950               if(c >= 'a') { /* black rights */
16951                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
16952                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16953                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16954                   if(i == BOARD_RGHT) break;
16955                   board[CASTLING][5] = i;
16956                   c -= AAA;
16957                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16958                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16959                   if(c > i)
16960                       board[CASTLING][3] = c;
16961                   else
16962                       board[CASTLING][4] = c;
16963               } else { /* white rights */
16964                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
16965                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16966                     if(board[0][i] == WhiteKing) break;
16967                   if(i == BOARD_RGHT) break;
16968                   board[CASTLING][2] = i;
16969                   c -= AAA - 'a' + 'A';
16970                   if(board[0][c] >= WhiteKing) break;
16971                   if(c > i)
16972                       board[CASTLING][0] = c;
16973                   else
16974                       board[CASTLING][1] = c;
16975               }
16976         }
16977       }
16978       for(i=0; i<nrCastlingRights; i++)
16979         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16980       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
16981     if (appData.debugMode) {
16982         fprintf(debugFP, "FEN castling rights:");
16983         for(i=0; i<nrCastlingRights; i++)
16984         fprintf(debugFP, " %d", board[CASTLING][i]);
16985         fprintf(debugFP, "\n");
16986     }
16987
16988       while(*p==' ') p++;
16989     }
16990
16991     /* read e.p. field in games that know e.p. capture */
16992     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16993        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16994       if(*p=='-') {
16995         p++; board[EP_STATUS] = EP_NONE;
16996       } else {
16997          char c = *p++ - AAA;
16998
16999          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17000          if(*p >= '0' && *p <='9') p++;
17001          board[EP_STATUS] = c;
17002       }
17003     }
17004
17005
17006     if(sscanf(p, "%d", &i) == 1) {
17007         FENrulePlies = i; /* 50-move ply counter */
17008         /* (The move number is still ignored)    */
17009     }
17010
17011     return TRUE;
17012 }
17013
17014 void
17015 EditPositionPasteFEN (char *fen)
17016 {
17017   if (fen != NULL) {
17018     Board initial_position;
17019
17020     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17021       DisplayError(_("Bad FEN position in clipboard"), 0);
17022       return ;
17023     } else {
17024       int savedBlackPlaysFirst = blackPlaysFirst;
17025       EditPositionEvent();
17026       blackPlaysFirst = savedBlackPlaysFirst;
17027       CopyBoard(boards[0], initial_position);
17028       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17029       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17030       DisplayBothClocks();
17031       DrawPosition(FALSE, boards[currentMove]);
17032     }
17033   }
17034 }
17035
17036 static char cseq[12] = "\\   ";
17037
17038 Boolean
17039 set_cont_sequence (char *new_seq)
17040 {
17041     int len;
17042     Boolean ret;
17043
17044     // handle bad attempts to set the sequence
17045         if (!new_seq)
17046                 return 0; // acceptable error - no debug
17047
17048     len = strlen(new_seq);
17049     ret = (len > 0) && (len < sizeof(cseq));
17050     if (ret)
17051       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17052     else if (appData.debugMode)
17053       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17054     return ret;
17055 }
17056
17057 /*
17058     reformat a source message so words don't cross the width boundary.  internal
17059     newlines are not removed.  returns the wrapped size (no null character unless
17060     included in source message).  If dest is NULL, only calculate the size required
17061     for the dest buffer.  lp argument indicats line position upon entry, and it's
17062     passed back upon exit.
17063 */
17064 int
17065 wrap (char *dest, char *src, int count, int width, int *lp)
17066 {
17067     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17068
17069     cseq_len = strlen(cseq);
17070     old_line = line = *lp;
17071     ansi = len = clen = 0;
17072
17073     for (i=0; i < count; i++)
17074     {
17075         if (src[i] == '\033')
17076             ansi = 1;
17077
17078         // if we hit the width, back up
17079         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17080         {
17081             // store i & len in case the word is too long
17082             old_i = i, old_len = len;
17083
17084             // find the end of the last word
17085             while (i && src[i] != ' ' && src[i] != '\n')
17086             {
17087                 i--;
17088                 len--;
17089             }
17090
17091             // word too long?  restore i & len before splitting it
17092             if ((old_i-i+clen) >= width)
17093             {
17094                 i = old_i;
17095                 len = old_len;
17096             }
17097
17098             // extra space?
17099             if (i && src[i-1] == ' ')
17100                 len--;
17101
17102             if (src[i] != ' ' && src[i] != '\n')
17103             {
17104                 i--;
17105                 if (len)
17106                     len--;
17107             }
17108
17109             // now append the newline and continuation sequence
17110             if (dest)
17111                 dest[len] = '\n';
17112             len++;
17113             if (dest)
17114                 strncpy(dest+len, cseq, cseq_len);
17115             len += cseq_len;
17116             line = cseq_len;
17117             clen = cseq_len;
17118             continue;
17119         }
17120
17121         if (dest)
17122             dest[len] = src[i];
17123         len++;
17124         if (!ansi)
17125             line++;
17126         if (src[i] == '\n')
17127             line = 0;
17128         if (src[i] == 'm')
17129             ansi = 0;
17130     }
17131     if (dest && appData.debugMode)
17132     {
17133         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17134             count, width, line, len, *lp);
17135         show_bytes(debugFP, src, count);
17136         fprintf(debugFP, "\ndest: ");
17137         show_bytes(debugFP, dest, len);
17138         fprintf(debugFP, "\n");
17139     }
17140     *lp = dest ? line : old_line;
17141
17142     return len;
17143 }
17144
17145 // [HGM] vari: routines for shelving variations
17146 Boolean modeRestore = FALSE;
17147
17148 void
17149 PushInner (int firstMove, int lastMove)
17150 {
17151         int i, j, nrMoves = lastMove - firstMove;
17152
17153         // push current tail of game on stack
17154         savedResult[storedGames] = gameInfo.result;
17155         savedDetails[storedGames] = gameInfo.resultDetails;
17156         gameInfo.resultDetails = NULL;
17157         savedFirst[storedGames] = firstMove;
17158         savedLast [storedGames] = lastMove;
17159         savedFramePtr[storedGames] = framePtr;
17160         framePtr -= nrMoves; // reserve space for the boards
17161         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17162             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17163             for(j=0; j<MOVE_LEN; j++)
17164                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17165             for(j=0; j<2*MOVE_LEN; j++)
17166                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17167             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17168             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17169             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17170             pvInfoList[firstMove+i-1].depth = 0;
17171             commentList[framePtr+i] = commentList[firstMove+i];
17172             commentList[firstMove+i] = NULL;
17173         }
17174
17175         storedGames++;
17176         forwardMostMove = firstMove; // truncate game so we can start variation
17177 }
17178
17179 void
17180 PushTail (int firstMove, int lastMove)
17181 {
17182         if(appData.icsActive) { // only in local mode
17183                 forwardMostMove = currentMove; // mimic old ICS behavior
17184                 return;
17185         }
17186         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17187
17188         PushInner(firstMove, lastMove);
17189         if(storedGames == 1) GreyRevert(FALSE);
17190         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17191 }
17192
17193 void
17194 PopInner (Boolean annotate)
17195 {
17196         int i, j, nrMoves;
17197         char buf[8000], moveBuf[20];
17198
17199         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17200         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17201         nrMoves = savedLast[storedGames] - currentMove;
17202         if(annotate) {
17203                 int cnt = 10;
17204                 if(!WhiteOnMove(currentMove))
17205                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17206                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17207                 for(i=currentMove; i<forwardMostMove; i++) {
17208                         if(WhiteOnMove(i))
17209                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17210                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17211                         strcat(buf, moveBuf);
17212                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17213                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17214                 }
17215                 strcat(buf, ")");
17216         }
17217         for(i=1; i<=nrMoves; i++) { // copy last variation back
17218             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17219             for(j=0; j<MOVE_LEN; j++)
17220                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17221             for(j=0; j<2*MOVE_LEN; j++)
17222                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17223             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17224             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17225             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17226             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17227             commentList[currentMove+i] = commentList[framePtr+i];
17228             commentList[framePtr+i] = NULL;
17229         }
17230         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17231         framePtr = savedFramePtr[storedGames];
17232         gameInfo.result = savedResult[storedGames];
17233         if(gameInfo.resultDetails != NULL) {
17234             free(gameInfo.resultDetails);
17235       }
17236         gameInfo.resultDetails = savedDetails[storedGames];
17237         forwardMostMove = currentMove + nrMoves;
17238 }
17239
17240 Boolean
17241 PopTail (Boolean annotate)
17242 {
17243         if(appData.icsActive) return FALSE; // only in local mode
17244         if(!storedGames) return FALSE; // sanity
17245         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17246
17247         PopInner(annotate);
17248         if(currentMove < forwardMostMove) ForwardEvent(); else
17249         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17250
17251         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17252         return TRUE;
17253 }
17254
17255 void
17256 CleanupTail ()
17257 {       // remove all shelved variations
17258         int i;
17259         for(i=0; i<storedGames; i++) {
17260             if(savedDetails[i])
17261                 free(savedDetails[i]);
17262             savedDetails[i] = NULL;
17263         }
17264         for(i=framePtr; i<MAX_MOVES; i++) {
17265                 if(commentList[i]) free(commentList[i]);
17266                 commentList[i] = NULL;
17267         }
17268         framePtr = MAX_MOVES-1;
17269         storedGames = 0;
17270 }
17271
17272 void
17273 LoadVariation (int index, char *text)
17274 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17275         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17276         int level = 0, move;
17277
17278         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17279         // first find outermost bracketing variation
17280         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17281             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17282                 if(*p == '{') wait = '}'; else
17283                 if(*p == '[') wait = ']'; else
17284                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17285                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17286             }
17287             if(*p == wait) wait = NULLCHAR; // closing ]} found
17288             p++;
17289         }
17290         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17291         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17292         end[1] = NULLCHAR; // clip off comment beyond variation
17293         ToNrEvent(currentMove-1);
17294         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17295         // kludge: use ParsePV() to append variation to game
17296         move = currentMove;
17297         ParsePV(start, TRUE, TRUE);
17298         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17299         ClearPremoveHighlights();
17300         CommentPopDown();
17301         ToNrEvent(currentMove+1);
17302 }
17303