28d40009867e5039945dfaac0675607c2cd4e4ed
[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 enum ICS_TYPE 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         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5139             int len = strlen(moveList[i]);
5140             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5141             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5142         }
5143         SendToICS(buf);
5144     }
5145     SendToICS(ics_prefix);
5146     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5147 }
5148
5149 void
5150 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5151 {
5152     if (rf == DROP_RANK) {
5153       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5154       sprintf(move, "%c@%c%c\n",
5155                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5156     } else {
5157         if (promoChar == 'x' || promoChar == NULLCHAR) {
5158           sprintf(move, "%c%c%c%c\n",
5159                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5160         } else {
5161             sprintf(move, "%c%c%c%c%c\n",
5162                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5163         }
5164     }
5165 }
5166
5167 void
5168 ProcessICSInitScript (FILE *f)
5169 {
5170     char buf[MSG_SIZ];
5171
5172     while (fgets(buf, MSG_SIZ, f)) {
5173         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5174     }
5175
5176     fclose(f);
5177 }
5178
5179
5180 static int lastX, lastY, selectFlag, dragging;
5181
5182 void
5183 Sweep (int step)
5184 {
5185     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5186     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5187     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5188     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5189     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5190     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5191     do {
5192         promoSweep -= step;
5193         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5194         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5195         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5196         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5197         if(!step) step = -1;
5198     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5199             appData.testLegality && (promoSweep == king ||
5200             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5201     if(toX >= 0) {
5202         int victim = boards[currentMove][toY][toX];
5203         boards[currentMove][toY][toX] = promoSweep;
5204         DrawPosition(FALSE, boards[currentMove]);
5205         boards[currentMove][toY][toX] = victim;
5206     } else
5207     ChangeDragPiece(promoSweep);
5208 }
5209
5210 int
5211 PromoScroll (int x, int y)
5212 {
5213   int step = 0;
5214
5215   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5216   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5217   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5218   if(!step) return FALSE;
5219   lastX = x; lastY = y;
5220   if((promoSweep < BlackPawn) == flipView) step = -step;
5221   if(step > 0) selectFlag = 1;
5222   if(!selectFlag) Sweep(step);
5223   return FALSE;
5224 }
5225
5226 void
5227 NextPiece (int step)
5228 {
5229     ChessSquare piece = boards[currentMove][toY][toX];
5230     do {
5231         pieceSweep -= step;
5232         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5233         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5234         if(!step) step = -1;
5235     } while(PieceToChar(pieceSweep) == '.');
5236     boards[currentMove][toY][toX] = pieceSweep;
5237     DrawPosition(FALSE, boards[currentMove]);
5238     boards[currentMove][toY][toX] = piece;
5239 }
5240 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5241 void
5242 AlphaRank (char *move, int n)
5243 {
5244 //    char *p = move, c; int x, y;
5245
5246     if (appData.debugMode) {
5247         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5248     }
5249
5250     if(move[1]=='*' &&
5251        move[2]>='0' && move[2]<='9' &&
5252        move[3]>='a' && move[3]<='x'    ) {
5253         move[1] = '@';
5254         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5255         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5256     } else
5257     if(move[0]>='0' && move[0]<='9' &&
5258        move[1]>='a' && move[1]<='x' &&
5259        move[2]>='0' && move[2]<='9' &&
5260        move[3]>='a' && move[3]<='x'    ) {
5261         /* input move, Shogi -> normal */
5262         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5263         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5264         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5265         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5266     } else
5267     if(move[1]=='@' &&
5268        move[3]>='0' && move[3]<='9' &&
5269        move[2]>='a' && move[2]<='x'    ) {
5270         move[1] = '*';
5271         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5272         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5273     } else
5274     if(
5275        move[0]>='a' && move[0]<='x' &&
5276        move[3]>='0' && move[3]<='9' &&
5277        move[2]>='a' && move[2]<='x'    ) {
5278          /* output move, normal -> Shogi */
5279         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5280         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5281         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5282         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5283         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5284     }
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "   out = '%s'\n", move);
5287     }
5288 }
5289
5290 char yy_textstr[8000];
5291
5292 /* Parser for moves from gnuchess, ICS, or user typein box */
5293 Boolean
5294 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5295 {
5296     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5297
5298     switch (*moveType) {
5299       case WhitePromotion:
5300       case BlackPromotion:
5301       case WhiteNonPromotion:
5302       case BlackNonPromotion:
5303       case NormalMove:
5304       case WhiteCapturesEnPassant:
5305       case BlackCapturesEnPassant:
5306       case WhiteKingSideCastle:
5307       case WhiteQueenSideCastle:
5308       case BlackKingSideCastle:
5309       case BlackQueenSideCastle:
5310       case WhiteKingSideCastleWild:
5311       case WhiteQueenSideCastleWild:
5312       case BlackKingSideCastleWild:
5313       case BlackQueenSideCastleWild:
5314       /* Code added by Tord: */
5315       case WhiteHSideCastleFR:
5316       case WhiteASideCastleFR:
5317       case BlackHSideCastleFR:
5318       case BlackASideCastleFR:
5319       /* End of code added by Tord */
5320       case IllegalMove:         /* bug or odd chess variant */
5321         *fromX = currentMoveString[0] - AAA;
5322         *fromY = currentMoveString[1] - ONE;
5323         *toX = currentMoveString[2] - AAA;
5324         *toY = currentMoveString[3] - ONE;
5325         *promoChar = currentMoveString[4];
5326         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5327             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5328     if (appData.debugMode) {
5329         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5330     }
5331             *fromX = *fromY = *toX = *toY = 0;
5332             return FALSE;
5333         }
5334         if (appData.testLegality) {
5335           return (*moveType != IllegalMove);
5336         } else {
5337           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5338                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5339         }
5340
5341       case WhiteDrop:
5342       case BlackDrop:
5343         *fromX = *moveType == WhiteDrop ?
5344           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5345           (int) CharToPiece(ToLower(currentMoveString[0]));
5346         *fromY = DROP_RANK;
5347         *toX = currentMoveString[2] - AAA;
5348         *toY = currentMoveString[3] - ONE;
5349         *promoChar = NULLCHAR;
5350         return TRUE;
5351
5352       case AmbiguousMove:
5353       case ImpossibleMove:
5354       case EndOfFile:
5355       case ElapsedTime:
5356       case Comment:
5357       case PGNTag:
5358       case NAG:
5359       case WhiteWins:
5360       case BlackWins:
5361       case GameIsDrawn:
5362       default:
5363     if (appData.debugMode) {
5364         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5365     }
5366         /* bug? */
5367         *fromX = *fromY = *toX = *toY = 0;
5368         *promoChar = NULLCHAR;
5369         return FALSE;
5370     }
5371 }
5372
5373 Boolean pushed = FALSE;
5374 char *lastParseAttempt;
5375
5376 void
5377 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5378 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5379   int fromX, fromY, toX, toY; char promoChar;
5380   ChessMove moveType;
5381   Boolean valid;
5382   int nr = 0;
5383
5384   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5385     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5386     pushed = TRUE;
5387   }
5388   endPV = forwardMostMove;
5389   do {
5390     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5391     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5392     lastParseAttempt = pv;
5393     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5394     if(!valid && nr == 0 &&
5395        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5396         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5397         // Hande case where played move is different from leading PV move
5398         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5399         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5400         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5401         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5402           endPV += 2; // if position different, keep this
5403           moveList[endPV-1][0] = fromX + AAA;
5404           moveList[endPV-1][1] = fromY + ONE;
5405           moveList[endPV-1][2] = toX + AAA;
5406           moveList[endPV-1][3] = toY + ONE;
5407           parseList[endPV-1][0] = NULLCHAR;
5408           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5409         }
5410       }
5411     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5412     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5413     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5414     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5415         valid++; // allow comments in PV
5416         continue;
5417     }
5418     nr++;
5419     if(endPV+1 > framePtr) break; // no space, truncate
5420     if(!valid) break;
5421     endPV++;
5422     CopyBoard(boards[endPV], boards[endPV-1]);
5423     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5424     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5425     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5426     CoordsToAlgebraic(boards[endPV - 1],
5427                              PosFlags(endPV - 1),
5428                              fromY, fromX, toY, toX, promoChar,
5429                              parseList[endPV - 1]);
5430   } while(valid);
5431   if(atEnd == 2) return; // used hidden, for PV conversion
5432   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5433   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5434   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5435                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5436   DrawPosition(TRUE, boards[currentMove]);
5437 }
5438
5439 int
5440 MultiPV (ChessProgramState *cps)
5441 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5442         int i;
5443         for(i=0; i<cps->nrOptions; i++)
5444             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5445                 return i;
5446         return -1;
5447 }
5448
5449 Boolean
5450 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5451 {
5452         int startPV, multi, lineStart, origIndex = index;
5453         char *p, buf2[MSG_SIZ];
5454
5455         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5456         lastX = x; lastY = y;
5457         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5458         lineStart = startPV = index;
5459         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5460         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5461         index = startPV;
5462         do{ while(buf[index] && buf[index] != '\n') index++;
5463         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5464         buf[index] = 0;
5465         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5466                 int n = first.option[multi].value;
5467                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5468                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5469                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5470                 first.option[multi].value = n;
5471                 *start = *end = 0;
5472                 return FALSE;
5473         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5474                 ExcludeClick(origIndex - lineStart);
5475                 return FALSE;
5476         }
5477         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5478         *start = startPV; *end = index-1;
5479         return TRUE;
5480 }
5481
5482 char *
5483 PvToSAN (char *pv)
5484 {
5485         static char buf[10*MSG_SIZ];
5486         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5487         *buf = NULLCHAR;
5488         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5489         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5490         for(i = forwardMostMove; i<endPV; i++){
5491             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5492             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5493             k += strlen(buf+k);
5494         }
5495         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5496         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5497         endPV = savedEnd;
5498         return buf;
5499 }
5500
5501 Boolean
5502 LoadPV (int x, int y)
5503 { // called on right mouse click to load PV
5504   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5505   lastX = x; lastY = y;
5506   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5507   return TRUE;
5508 }
5509
5510 void
5511 UnLoadPV ()
5512 {
5513   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5514   if(endPV < 0) return;
5515   if(appData.autoCopyPV) CopyFENToClipboard();
5516   endPV = -1;
5517   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5518         Boolean saveAnimate = appData.animate;
5519         if(pushed) {
5520             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5521                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5522             } else storedGames--; // abandon shelved tail of original game
5523         }
5524         pushed = FALSE;
5525         forwardMostMove = currentMove;
5526         currentMove = oldFMM;
5527         appData.animate = FALSE;
5528         ToNrEvent(forwardMostMove);
5529         appData.animate = saveAnimate;
5530   }
5531   currentMove = forwardMostMove;
5532   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5533   ClearPremoveHighlights();
5534   DrawPosition(TRUE, boards[currentMove]);
5535 }
5536
5537 void
5538 MovePV (int x, int y, int h)
5539 { // step through PV based on mouse coordinates (called on mouse move)
5540   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5541
5542   // we must somehow check if right button is still down (might be released off board!)
5543   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5544   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5545   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5546   if(!step) return;
5547   lastX = x; lastY = y;
5548
5549   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5550   if(endPV < 0) return;
5551   if(y < margin) step = 1; else
5552   if(y > h - margin) step = -1;
5553   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5554   currentMove += step;
5555   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5556   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5557                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5558   DrawPosition(FALSE, boards[currentMove]);
5559 }
5560
5561
5562 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5563 // All positions will have equal probability, but the current method will not provide a unique
5564 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5565 #define DARK 1
5566 #define LITE 2
5567 #define ANY 3
5568
5569 int squaresLeft[4];
5570 int piecesLeft[(int)BlackPawn];
5571 int seed, nrOfShuffles;
5572
5573 void
5574 GetPositionNumber ()
5575 {       // sets global variable seed
5576         int i;
5577
5578         seed = appData.defaultFrcPosition;
5579         if(seed < 0) { // randomize based on time for negative FRC position numbers
5580                 for(i=0; i<50; i++) seed += random();
5581                 seed = random() ^ random() >> 8 ^ random() << 8;
5582                 if(seed<0) seed = -seed;
5583         }
5584 }
5585
5586 int
5587 put (Board board, int pieceType, int rank, int n, int shade)
5588 // put the piece on the (n-1)-th empty squares of the given shade
5589 {
5590         int i;
5591
5592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5593                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5594                         board[rank][i] = (ChessSquare) pieceType;
5595                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5596                         squaresLeft[ANY]--;
5597                         piecesLeft[pieceType]--;
5598                         return i;
5599                 }
5600         }
5601         return -1;
5602 }
5603
5604
5605 void
5606 AddOnePiece (Board board, int pieceType, int rank, int shade)
5607 // calculate where the next piece goes, (any empty square), and put it there
5608 {
5609         int i;
5610
5611         i = seed % squaresLeft[shade];
5612         nrOfShuffles *= squaresLeft[shade];
5613         seed /= squaresLeft[shade];
5614         put(board, pieceType, rank, i, shade);
5615 }
5616
5617 void
5618 AddTwoPieces (Board board, int pieceType, int rank)
5619 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5620 {
5621         int i, n=squaresLeft[ANY], j=n-1, k;
5622
5623         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5624         i = seed % k;  // pick one
5625         nrOfShuffles *= k;
5626         seed /= k;
5627         while(i >= j) i -= j--;
5628         j = n - 1 - j; i += j;
5629         put(board, pieceType, rank, j, ANY);
5630         put(board, pieceType, rank, i, ANY);
5631 }
5632
5633 void
5634 SetUpShuffle (Board board, int number)
5635 {
5636         int i, p, first=1;
5637
5638         GetPositionNumber(); nrOfShuffles = 1;
5639
5640         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5641         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5642         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5643
5644         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5645
5646         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5647             p = (int) board[0][i];
5648             if(p < (int) BlackPawn) piecesLeft[p] ++;
5649             board[0][i] = EmptySquare;
5650         }
5651
5652         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5653             // shuffles restricted to allow normal castling put KRR first
5654             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5655                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5656             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5657                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5658             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5659                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5660             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5661                 put(board, WhiteRook, 0, 0, ANY);
5662             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5663         }
5664
5665         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5666             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5667             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5668                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5669                 while(piecesLeft[p] >= 2) {
5670                     AddOnePiece(board, p, 0, LITE);
5671                     AddOnePiece(board, p, 0, DARK);
5672                 }
5673                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5674             }
5675
5676         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5677             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5678             // but we leave King and Rooks for last, to possibly obey FRC restriction
5679             if(p == (int)WhiteRook) continue;
5680             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5681             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5682         }
5683
5684         // now everything is placed, except perhaps King (Unicorn) and Rooks
5685
5686         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5687             // Last King gets castling rights
5688             while(piecesLeft[(int)WhiteUnicorn]) {
5689                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5690                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5691             }
5692
5693             while(piecesLeft[(int)WhiteKing]) {
5694                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5695                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5696             }
5697
5698
5699         } else {
5700             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5701             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5702         }
5703
5704         // Only Rooks can be left; simply place them all
5705         while(piecesLeft[(int)WhiteRook]) {
5706                 i = put(board, WhiteRook, 0, 0, ANY);
5707                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5708                         if(first) {
5709                                 first=0;
5710                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5711                         }
5712                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5713                 }
5714         }
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5716             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5717         }
5718
5719         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5720 }
5721
5722 int
5723 SetCharTable (char *table, const char * map)
5724 /* [HGM] moved here from winboard.c because of its general usefulness */
5725 /*       Basically a safe strcpy that uses the last character as King */
5726 {
5727     int result = FALSE; int NrPieces;
5728
5729     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5730                     && NrPieces >= 12 && !(NrPieces&1)) {
5731         int i; /* [HGM] Accept even length from 12 to 34 */
5732
5733         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5734         for( i=0; i<NrPieces/2-1; i++ ) {
5735             table[i] = map[i];
5736             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5737         }
5738         table[(int) WhiteKing]  = map[NrPieces/2-1];
5739         table[(int) BlackKing]  = map[NrPieces-1];
5740
5741         result = TRUE;
5742     }
5743
5744     return result;
5745 }
5746
5747 void
5748 Prelude (Board board)
5749 {       // [HGM] superchess: random selection of exo-pieces
5750         int i, j, k; ChessSquare p;
5751         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5752
5753         GetPositionNumber(); // use FRC position number
5754
5755         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5756             SetCharTable(pieceToChar, appData.pieceToCharTable);
5757             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5758                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5759         }
5760
5761         j = seed%4;                 seed /= 4;
5762         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5763         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5764         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5765         j = seed%3 + (seed%3 >= j); seed /= 3;
5766         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5767         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5768         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5769         j = seed%3;                 seed /= 3;
5770         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5771         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5772         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5773         j = seed%2 + (seed%2 >= j); seed /= 2;
5774         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5775         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5776         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5777         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5778         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5779         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5780         put(board, exoPieces[0],    0, 0, ANY);
5781         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5782 }
5783
5784 void
5785 InitPosition (int redraw)
5786 {
5787     ChessSquare (* pieces)[BOARD_FILES];
5788     int i, j, pawnRow, overrule,
5789     oldx = gameInfo.boardWidth,
5790     oldy = gameInfo.boardHeight,
5791     oldh = gameInfo.holdingsWidth;
5792     static int oldv;
5793
5794     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5795
5796     /* [AS] Initialize pv info list [HGM] and game status */
5797     {
5798         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5799             pvInfoList[i].depth = 0;
5800             boards[i][EP_STATUS] = EP_NONE;
5801             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5802         }
5803
5804         initialRulePlies = 0; /* 50-move counter start */
5805
5806         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5807         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5808     }
5809
5810
5811     /* [HGM] logic here is completely changed. In stead of full positions */
5812     /* the initialized data only consist of the two backranks. The switch */
5813     /* selects which one we will use, which is than copied to the Board   */
5814     /* initialPosition, which for the rest is initialized by Pawns and    */
5815     /* empty squares. This initial position is then copied to boards[0],  */
5816     /* possibly after shuffling, so that it remains available.            */
5817
5818     gameInfo.holdingsWidth = 0; /* default board sizes */
5819     gameInfo.boardWidth    = 8;
5820     gameInfo.boardHeight   = 8;
5821     gameInfo.holdingsSize  = 0;
5822     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5823     for(i=0; i<BOARD_FILES-2; i++)
5824       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5825     initialPosition[EP_STATUS] = EP_NONE;
5826     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5827     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5828          SetCharTable(pieceNickName, appData.pieceNickNames);
5829     else SetCharTable(pieceNickName, "............");
5830     pieces = FIDEArray;
5831
5832     switch (gameInfo.variant) {
5833     case VariantFischeRandom:
5834       shuffleOpenings = TRUE;
5835     default:
5836       break;
5837     case VariantShatranj:
5838       pieces = ShatranjArray;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5841       break;
5842     case VariantMakruk:
5843       pieces = makrukArray;
5844       nrCastlingRights = 0;
5845       startedFromSetupPosition = TRUE;
5846       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5847       break;
5848     case VariantTwoKings:
5849       pieces = twoKingsArray;
5850       break;
5851     case VariantGrand:
5852       pieces = GrandArray;
5853       nrCastlingRights = 0;
5854       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5855       gameInfo.boardWidth = 10;
5856       gameInfo.boardHeight = 10;
5857       gameInfo.holdingsSize = 7;
5858       break;
5859     case VariantCapaRandom:
5860       shuffleOpenings = TRUE;
5861     case VariantCapablanca:
5862       pieces = CapablancaArray;
5863       gameInfo.boardWidth = 10;
5864       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865       break;
5866     case VariantGothic:
5867       pieces = GothicArray;
5868       gameInfo.boardWidth = 10;
5869       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5870       break;
5871     case VariantSChess:
5872       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5873       gameInfo.holdingsSize = 7;
5874       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5875       break;
5876     case VariantJanus:
5877       pieces = JanusArray;
5878       gameInfo.boardWidth = 10;
5879       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5880       nrCastlingRights = 6;
5881         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5882         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5883         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5884         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5885         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5886         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5887       break;
5888     case VariantFalcon:
5889       pieces = FalconArray;
5890       gameInfo.boardWidth = 10;
5891       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5892       break;
5893     case VariantXiangqi:
5894       pieces = XiangqiArray;
5895       gameInfo.boardWidth  = 9;
5896       gameInfo.boardHeight = 10;
5897       nrCastlingRights = 0;
5898       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5899       break;
5900     case VariantShogi:
5901       pieces = ShogiArray;
5902       gameInfo.boardWidth  = 9;
5903       gameInfo.boardHeight = 9;
5904       gameInfo.holdingsSize = 7;
5905       nrCastlingRights = 0;
5906       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5907       break;
5908     case VariantCourier:
5909       pieces = CourierArray;
5910       gameInfo.boardWidth  = 12;
5911       nrCastlingRights = 0;
5912       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5913       break;
5914     case VariantKnightmate:
5915       pieces = KnightmateArray;
5916       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5917       break;
5918     case VariantSpartan:
5919       pieces = SpartanArray;
5920       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5921       break;
5922     case VariantFairy:
5923       pieces = fairyArray;
5924       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5925       break;
5926     case VariantGreat:
5927       pieces = GreatArray;
5928       gameInfo.boardWidth = 10;
5929       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5930       gameInfo.holdingsSize = 8;
5931       break;
5932     case VariantSuper:
5933       pieces = FIDEArray;
5934       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5935       gameInfo.holdingsSize = 8;
5936       startedFromSetupPosition = TRUE;
5937       break;
5938     case VariantCrazyhouse:
5939     case VariantBughouse:
5940       pieces = FIDEArray;
5941       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5942       gameInfo.holdingsSize = 5;
5943       break;
5944     case VariantWildCastle:
5945       pieces = FIDEArray;
5946       /* !!?shuffle with kings guaranteed to be on d or e file */
5947       shuffleOpenings = 1;
5948       break;
5949     case VariantNoCastle:
5950       pieces = FIDEArray;
5951       nrCastlingRights = 0;
5952       /* !!?unconstrained back-rank shuffle */
5953       shuffleOpenings = 1;
5954       break;
5955     }
5956
5957     overrule = 0;
5958     if(appData.NrFiles >= 0) {
5959         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5960         gameInfo.boardWidth = appData.NrFiles;
5961     }
5962     if(appData.NrRanks >= 0) {
5963         gameInfo.boardHeight = appData.NrRanks;
5964     }
5965     if(appData.holdingsSize >= 0) {
5966         i = appData.holdingsSize;
5967         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5968         gameInfo.holdingsSize = i;
5969     }
5970     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5971     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5972         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5973
5974     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5975     if(pawnRow < 1) pawnRow = 1;
5976     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5977
5978     /* User pieceToChar list overrules defaults */
5979     if(appData.pieceToCharTable != NULL)
5980         SetCharTable(pieceToChar, appData.pieceToCharTable);
5981
5982     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5983
5984         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5985             s = (ChessSquare) 0; /* account holding counts in guard band */
5986         for( i=0; i<BOARD_HEIGHT; i++ )
5987             initialPosition[i][j] = s;
5988
5989         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5990         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5991         initialPosition[pawnRow][j] = WhitePawn;
5992         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5993         if(gameInfo.variant == VariantXiangqi) {
5994             if(j&1) {
5995                 initialPosition[pawnRow][j] =
5996                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5997                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5998                    initialPosition[2][j] = WhiteCannon;
5999                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6000                 }
6001             }
6002         }
6003         if(gameInfo.variant == VariantGrand) {
6004             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6005                initialPosition[0][j] = WhiteRook;
6006                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6007             }
6008         }
6009         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6010     }
6011     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6012
6013             j=BOARD_LEFT+1;
6014             initialPosition[1][j] = WhiteBishop;
6015             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6016             j=BOARD_RGHT-2;
6017             initialPosition[1][j] = WhiteRook;
6018             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6019     }
6020
6021     if( nrCastlingRights == -1) {
6022         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6023         /*       This sets default castling rights from none to normal corners   */
6024         /* Variants with other castling rights must set them themselves above    */
6025         nrCastlingRights = 6;
6026
6027         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6028         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6029         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6030         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6031         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6032         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6033      }
6034
6035      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6036      if(gameInfo.variant == VariantGreat) { // promotion commoners
6037         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6038         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6039         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6040         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6041      }
6042      if( gameInfo.variant == VariantSChess ) {
6043       initialPosition[1][0] = BlackMarshall;
6044       initialPosition[2][0] = BlackAngel;
6045       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6046       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6047       initialPosition[1][1] = initialPosition[2][1] = 
6048       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6049      }
6050   if (appData.debugMode) {
6051     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6052   }
6053     if(shuffleOpenings) {
6054         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6055         startedFromSetupPosition = TRUE;
6056     }
6057     if(startedFromPositionFile) {
6058       /* [HGM] loadPos: use PositionFile for every new game */
6059       CopyBoard(initialPosition, filePosition);
6060       for(i=0; i<nrCastlingRights; i++)
6061           initialRights[i] = filePosition[CASTLING][i];
6062       startedFromSetupPosition = TRUE;
6063     }
6064
6065     CopyBoard(boards[0], initialPosition);
6066
6067     if(oldx != gameInfo.boardWidth ||
6068        oldy != gameInfo.boardHeight ||
6069        oldv != gameInfo.variant ||
6070        oldh != gameInfo.holdingsWidth
6071                                          )
6072             InitDrawingSizes(-2 ,0);
6073
6074     oldv = gameInfo.variant;
6075     if (redraw)
6076       DrawPosition(TRUE, boards[currentMove]);
6077 }
6078
6079 void
6080 SendBoard (ChessProgramState *cps, int moveNum)
6081 {
6082     char message[MSG_SIZ];
6083
6084     if (cps->useSetboard) {
6085       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6086       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6087       SendToProgram(message, cps);
6088       free(fen);
6089
6090     } else {
6091       ChessSquare *bp;
6092       int i, j, left=0, right=BOARD_WIDTH;
6093       /* Kludge to set black to move, avoiding the troublesome and now
6094        * deprecated "black" command.
6095        */
6096       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6097         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6098
6099       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6100
6101       SendToProgram("edit\n", cps);
6102       SendToProgram("#\n", cps);
6103       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6104         bp = &boards[moveNum][i][left];
6105         for (j = left; j < right; j++, bp++) {
6106           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6107           if ((int) *bp < (int) BlackPawn) {
6108             if(j == BOARD_RGHT+1)
6109                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6110             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6111             if(message[0] == '+' || message[0] == '~') {
6112               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6113                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6114                         AAA + j, ONE + i);
6115             }
6116             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6117                 message[1] = BOARD_RGHT   - 1 - j + '1';
6118                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6119             }
6120             SendToProgram(message, cps);
6121           }
6122         }
6123       }
6124
6125       SendToProgram("c\n", cps);
6126       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6127         bp = &boards[moveNum][i][left];
6128         for (j = left; j < right; j++, bp++) {
6129           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6130           if (((int) *bp != (int) EmptySquare)
6131               && ((int) *bp >= (int) BlackPawn)) {
6132             if(j == BOARD_LEFT-2)
6133                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6134             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6135                     AAA + j, ONE + i);
6136             if(message[0] == '+' || message[0] == '~') {
6137               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6138                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6139                         AAA + j, ONE + i);
6140             }
6141             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6142                 message[1] = BOARD_RGHT   - 1 - j + '1';
6143                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6144             }
6145             SendToProgram(message, cps);
6146           }
6147         }
6148       }
6149
6150       SendToProgram(".\n", cps);
6151     }
6152     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6153 }
6154
6155 char exclusionHeader[MSG_SIZ];
6156 int exCnt, excludePtr;
6157 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6158 static Exclusion excluTab[200];
6159 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6160
6161 static void
6162 WriteMap (int s)
6163 {
6164     int j;
6165     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6166     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6167 }
6168
6169 static void
6170 ClearMap ()
6171 {
6172     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6173     excludePtr = 24; exCnt = 0;
6174     WriteMap(0);
6175 }
6176
6177 static void
6178 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6179 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6180     char buf[2*MOVE_LEN], *p;
6181     Exclusion *e = excluTab;
6182     int i;
6183     for(i=0; i<exCnt; i++)
6184         if(e[i].ff == fromX && e[i].fr == fromY &&
6185            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6186     if(i == exCnt) { // was not in exclude list; add it
6187         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6188         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6189             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6190             return; // abort
6191         }
6192         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6193         excludePtr++; e[i].mark = excludePtr++;
6194         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6195         exCnt++;
6196     }
6197     exclusionHeader[e[i].mark] = state;
6198 }
6199
6200 static int
6201 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6202 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6203     char buf[MSG_SIZ];
6204     int j, k;
6205     ChessMove moveType;
6206     if((signed char)promoChar == -1) { // kludge to indicate best move
6207         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6208             return 1; // if unparsable, abort
6209     }
6210     // update exclusion map (resolving toggle by consulting existing state)
6211     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6212     j = k%8; k >>= 3;
6213     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6214     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6215          excludeMap[k] |=   1<<j;
6216     else excludeMap[k] &= ~(1<<j);
6217     // update header
6218     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6219     // inform engine
6220     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6221     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6222     SendToProgram(buf, &first);
6223     return (state == '+');
6224 }
6225
6226 static void
6227 ExcludeClick (int index)
6228 {
6229     int i, j;
6230     Exclusion *e = excluTab;
6231     if(index < 25) { // none, best or tail clicked
6232         if(index < 13) { // none: include all
6233             WriteMap(0); // clear map
6234             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6235             SendToProgram("include all\n", &first); // and inform engine
6236         } else if(index > 18) { // tail
6237             if(exclusionHeader[19] == '-') { // tail was excluded
6238                 SendToProgram("include all\n", &first);
6239                 WriteMap(0); // clear map completely
6240                 // now re-exclude selected moves
6241                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6242                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6243             } else { // tail was included or in mixed state
6244                 SendToProgram("exclude all\n", &first);
6245                 WriteMap(0xFF); // fill map completely
6246                 // now re-include selected moves
6247                 j = 0; // count them
6248                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6249                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6250                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6251             }
6252         } else { // best
6253             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6254         }
6255     } else {
6256         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6257             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6258             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6259             break;
6260         }
6261     }
6262 }
6263
6264 ChessSquare
6265 DefaultPromoChoice (int white)
6266 {
6267     ChessSquare result;
6268     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6269         result = WhiteFerz; // no choice
6270     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6271         result= WhiteKing; // in Suicide Q is the last thing we want
6272     else if(gameInfo.variant == VariantSpartan)
6273         result = white ? WhiteQueen : WhiteAngel;
6274     else result = WhiteQueen;
6275     if(!white) result = WHITE_TO_BLACK result;
6276     return result;
6277 }
6278
6279 static int autoQueen; // [HGM] oneclick
6280
6281 int
6282 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6283 {
6284     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6285     /* [HGM] add Shogi promotions */
6286     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6287     ChessSquare piece;
6288     ChessMove moveType;
6289     Boolean premove;
6290
6291     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6292     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6293
6294     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6295       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6296         return FALSE;
6297
6298     piece = boards[currentMove][fromY][fromX];
6299     if(gameInfo.variant == VariantShogi) {
6300         promotionZoneSize = BOARD_HEIGHT/3;
6301         highestPromotingPiece = (int)WhiteFerz;
6302     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6303         promotionZoneSize = 3;
6304     }
6305
6306     // Treat Lance as Pawn when it is not representing Amazon
6307     if(gameInfo.variant != VariantSuper) {
6308         if(piece == WhiteLance) piece = WhitePawn; else
6309         if(piece == BlackLance) piece = BlackPawn;
6310     }
6311
6312     // next weed out all moves that do not touch the promotion zone at all
6313     if((int)piece >= BlackPawn) {
6314         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6315              return FALSE;
6316         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6317     } else {
6318         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6319            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6320     }
6321
6322     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6323
6324     // weed out mandatory Shogi promotions
6325     if(gameInfo.variant == VariantShogi) {
6326         if(piece >= BlackPawn) {
6327             if(toY == 0 && piece == BlackPawn ||
6328                toY == 0 && piece == BlackQueen ||
6329                toY <= 1 && piece == BlackKnight) {
6330                 *promoChoice = '+';
6331                 return FALSE;
6332             }
6333         } else {
6334             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6335                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6336                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6337                 *promoChoice = '+';
6338                 return FALSE;
6339             }
6340         }
6341     }
6342
6343     // weed out obviously illegal Pawn moves
6344     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6345         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6346         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6347         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6348         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6349         // note we are not allowed to test for valid (non-)capture, due to premove
6350     }
6351
6352     // we either have a choice what to promote to, or (in Shogi) whether to promote
6353     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6354         *promoChoice = PieceToChar(BlackFerz);  // no choice
6355         return FALSE;
6356     }
6357     // no sense asking what we must promote to if it is going to explode...
6358     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6359         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6360         return FALSE;
6361     }
6362     // give caller the default choice even if we will not make it
6363     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6364     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6365     if(        sweepSelect && gameInfo.variant != VariantGreat
6366                            && gameInfo.variant != VariantGrand
6367                            && gameInfo.variant != VariantSuper) return FALSE;
6368     if(autoQueen) return FALSE; // predetermined
6369
6370     // suppress promotion popup on illegal moves that are not premoves
6371     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6372               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6373     if(appData.testLegality && !premove) {
6374         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6375                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6376         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6377             return FALSE;
6378     }
6379
6380     return TRUE;
6381 }
6382
6383 int
6384 InPalace (int row, int column)
6385 {   /* [HGM] for Xiangqi */
6386     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6387          column < (BOARD_WIDTH + 4)/2 &&
6388          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6389     return FALSE;
6390 }
6391
6392 int
6393 PieceForSquare (int x, int y)
6394 {
6395   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6396      return -1;
6397   else
6398      return boards[currentMove][y][x];
6399 }
6400
6401 int
6402 OKToStartUserMove (int x, int y)
6403 {
6404     ChessSquare from_piece;
6405     int white_piece;
6406
6407     if (matchMode) return FALSE;
6408     if (gameMode == EditPosition) return TRUE;
6409
6410     if (x >= 0 && y >= 0)
6411       from_piece = boards[currentMove][y][x];
6412     else
6413       from_piece = EmptySquare;
6414
6415     if (from_piece == EmptySquare) return FALSE;
6416
6417     white_piece = (int)from_piece >= (int)WhitePawn &&
6418       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6419
6420     switch (gameMode) {
6421       case AnalyzeFile:
6422       case TwoMachinesPlay:
6423       case EndOfGame:
6424         return FALSE;
6425
6426       case IcsObserving:
6427       case IcsIdle:
6428         return FALSE;
6429
6430       case MachinePlaysWhite:
6431       case IcsPlayingBlack:
6432         if (appData.zippyPlay) return FALSE;
6433         if (white_piece) {
6434             DisplayMoveError(_("You are playing Black"));
6435             return FALSE;
6436         }
6437         break;
6438
6439       case MachinePlaysBlack:
6440       case IcsPlayingWhite:
6441         if (appData.zippyPlay) return FALSE;
6442         if (!white_piece) {
6443             DisplayMoveError(_("You are playing White"));
6444             return FALSE;
6445         }
6446         break;
6447
6448       case PlayFromGameFile:
6449             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6450       case EditGame:
6451         if (!white_piece && WhiteOnMove(currentMove)) {
6452             DisplayMoveError(_("It is White's turn"));
6453             return FALSE;
6454         }
6455         if (white_piece && !WhiteOnMove(currentMove)) {
6456             DisplayMoveError(_("It is Black's turn"));
6457             return FALSE;
6458         }
6459         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6460             /* Editing correspondence game history */
6461             /* Could disallow this or prompt for confirmation */
6462             cmailOldMove = -1;
6463         }
6464         break;
6465
6466       case BeginningOfGame:
6467         if (appData.icsActive) return FALSE;
6468         if (!appData.noChessProgram) {
6469             if (!white_piece) {
6470                 DisplayMoveError(_("You are playing White"));
6471                 return FALSE;
6472             }
6473         }
6474         break;
6475
6476       case Training:
6477         if (!white_piece && WhiteOnMove(currentMove)) {
6478             DisplayMoveError(_("It is White's turn"));
6479             return FALSE;
6480         }
6481         if (white_piece && !WhiteOnMove(currentMove)) {
6482             DisplayMoveError(_("It is Black's turn"));
6483             return FALSE;
6484         }
6485         break;
6486
6487       default:
6488       case IcsExamining:
6489         break;
6490     }
6491     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6492         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6493         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6494         && gameMode != AnalyzeFile && gameMode != Training) {
6495         DisplayMoveError(_("Displayed position is not current"));
6496         return FALSE;
6497     }
6498     return TRUE;
6499 }
6500
6501 Boolean
6502 OnlyMove (int *x, int *y, Boolean captures) 
6503 {
6504     DisambiguateClosure cl;
6505     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6506     switch(gameMode) {
6507       case MachinePlaysBlack:
6508       case IcsPlayingWhite:
6509       case BeginningOfGame:
6510         if(!WhiteOnMove(currentMove)) return FALSE;
6511         break;
6512       case MachinePlaysWhite:
6513       case IcsPlayingBlack:
6514         if(WhiteOnMove(currentMove)) return FALSE;
6515         break;
6516       case EditGame:
6517         break;
6518       default:
6519         return FALSE;
6520     }
6521     cl.pieceIn = EmptySquare;
6522     cl.rfIn = *y;
6523     cl.ffIn = *x;
6524     cl.rtIn = -1;
6525     cl.ftIn = -1;
6526     cl.promoCharIn = NULLCHAR;
6527     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6528     if( cl.kind == NormalMove ||
6529         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6530         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6531         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6532       fromX = cl.ff;
6533       fromY = cl.rf;
6534       *x = cl.ft;
6535       *y = cl.rt;
6536       return TRUE;
6537     }
6538     if(cl.kind != ImpossibleMove) return FALSE;
6539     cl.pieceIn = EmptySquare;
6540     cl.rfIn = -1;
6541     cl.ffIn = -1;
6542     cl.rtIn = *y;
6543     cl.ftIn = *x;
6544     cl.promoCharIn = NULLCHAR;
6545     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6546     if( cl.kind == NormalMove ||
6547         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6548         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6549         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6550       fromX = cl.ff;
6551       fromY = cl.rf;
6552       *x = cl.ft;
6553       *y = cl.rt;
6554       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6555       return TRUE;
6556     }
6557     return FALSE;
6558 }
6559
6560 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6561 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6562 int lastLoadGameUseList = FALSE;
6563 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6564 ChessMove lastLoadGameStart = EndOfFile;
6565 int doubleClick;
6566
6567 void
6568 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6569 {
6570     ChessMove moveType;
6571     ChessSquare pup;
6572     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6573
6574     /* Check if the user is playing in turn.  This is complicated because we
6575        let the user "pick up" a piece before it is his turn.  So the piece he
6576        tried to pick up may have been captured by the time he puts it down!
6577        Therefore we use the color the user is supposed to be playing in this
6578        test, not the color of the piece that is currently on the starting
6579        square---except in EditGame mode, where the user is playing both
6580        sides; fortunately there the capture race can't happen.  (It can
6581        now happen in IcsExamining mode, but that's just too bad.  The user
6582        will get a somewhat confusing message in that case.)
6583        */
6584
6585     switch (gameMode) {
6586       case AnalyzeFile:
6587       case TwoMachinesPlay:
6588       case EndOfGame:
6589       case IcsObserving:
6590       case IcsIdle:
6591         /* We switched into a game mode where moves are not accepted,
6592            perhaps while the mouse button was down. */
6593         return;
6594
6595       case MachinePlaysWhite:
6596         /* User is moving for Black */
6597         if (WhiteOnMove(currentMove)) {
6598             DisplayMoveError(_("It is White's turn"));
6599             return;
6600         }
6601         break;
6602
6603       case MachinePlaysBlack:
6604         /* User is moving for White */
6605         if (!WhiteOnMove(currentMove)) {
6606             DisplayMoveError(_("It is Black's turn"));
6607             return;
6608         }
6609         break;
6610
6611       case PlayFromGameFile:
6612             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6613       case EditGame:
6614       case IcsExamining:
6615       case BeginningOfGame:
6616       case AnalyzeMode:
6617       case Training:
6618         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6619         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6620             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6621             /* User is moving for Black */
6622             if (WhiteOnMove(currentMove)) {
6623                 DisplayMoveError(_("It is White's turn"));
6624                 return;
6625             }
6626         } else {
6627             /* User is moving for White */
6628             if (!WhiteOnMove(currentMove)) {
6629                 DisplayMoveError(_("It is Black's turn"));
6630                 return;
6631             }
6632         }
6633         break;
6634
6635       case IcsPlayingBlack:
6636         /* User is moving for Black */
6637         if (WhiteOnMove(currentMove)) {
6638             if (!appData.premove) {
6639                 DisplayMoveError(_("It is White's turn"));
6640             } else if (toX >= 0 && toY >= 0) {
6641                 premoveToX = toX;
6642                 premoveToY = toY;
6643                 premoveFromX = fromX;
6644                 premoveFromY = fromY;
6645                 premovePromoChar = promoChar;
6646                 gotPremove = 1;
6647                 if (appData.debugMode)
6648                     fprintf(debugFP, "Got premove: fromX %d,"
6649                             "fromY %d, toX %d, toY %d\n",
6650                             fromX, fromY, toX, toY);
6651             }
6652             return;
6653         }
6654         break;
6655
6656       case IcsPlayingWhite:
6657         /* User is moving for White */
6658         if (!WhiteOnMove(currentMove)) {
6659             if (!appData.premove) {
6660                 DisplayMoveError(_("It is Black's turn"));
6661             } else if (toX >= 0 && toY >= 0) {
6662                 premoveToX = toX;
6663                 premoveToY = toY;
6664                 premoveFromX = fromX;
6665                 premoveFromY = fromY;
6666                 premovePromoChar = promoChar;
6667                 gotPremove = 1;
6668                 if (appData.debugMode)
6669                     fprintf(debugFP, "Got premove: fromX %d,"
6670                             "fromY %d, toX %d, toY %d\n",
6671                             fromX, fromY, toX, toY);
6672             }
6673             return;
6674         }
6675         break;
6676
6677       default:
6678         break;
6679
6680       case EditPosition:
6681         /* EditPosition, empty square, or different color piece;
6682            click-click move is possible */
6683         if (toX == -2 || toY == -2) {
6684             boards[0][fromY][fromX] = EmptySquare;
6685             DrawPosition(FALSE, boards[currentMove]);
6686             return;
6687         } else if (toX >= 0 && toY >= 0) {
6688             boards[0][toY][toX] = boards[0][fromY][fromX];
6689             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6690                 if(boards[0][fromY][0] != EmptySquare) {
6691                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6692                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6693                 }
6694             } else
6695             if(fromX == BOARD_RGHT+1) {
6696                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6697                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6698                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6699                 }
6700             } else
6701             boards[0][fromY][fromX] = gatingPiece;
6702             DrawPosition(FALSE, boards[currentMove]);
6703             return;
6704         }
6705         return;
6706     }
6707
6708     if(toX < 0 || toY < 0) return;
6709     pup = boards[currentMove][toY][toX];
6710
6711     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6712     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6713          if( pup != EmptySquare ) return;
6714          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6715            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6716                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6717            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6718            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6719            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6720            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6721          fromY = DROP_RANK;
6722     }
6723
6724     /* [HGM] always test for legality, to get promotion info */
6725     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6726                                          fromY, fromX, toY, toX, promoChar);
6727
6728     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6729
6730     /* [HGM] but possibly ignore an IllegalMove result */
6731     if (appData.testLegality) {
6732         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6733             DisplayMoveError(_("Illegal move"));
6734             return;
6735         }
6736     }
6737
6738     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6739         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6740              ClearPremoveHighlights(); // was included
6741         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6742         return;
6743     }
6744
6745     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6746 }
6747
6748 /* Common tail of UserMoveEvent and DropMenuEvent */
6749 int
6750 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6751 {
6752     char *bookHit = 0;
6753
6754     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6755         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6756         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6757         if(WhiteOnMove(currentMove)) {
6758             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6759         } else {
6760             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6761         }
6762     }
6763
6764     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6765        move type in caller when we know the move is a legal promotion */
6766     if(moveType == NormalMove && promoChar)
6767         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6768
6769     /* [HGM] <popupFix> The following if has been moved here from
6770        UserMoveEvent(). Because it seemed to belong here (why not allow
6771        piece drops in training games?), and because it can only be
6772        performed after it is known to what we promote. */
6773     if (gameMode == Training) {
6774       /* compare the move played on the board to the next move in the
6775        * game. If they match, display the move and the opponent's response.
6776        * If they don't match, display an error message.
6777        */
6778       int saveAnimate;
6779       Board testBoard;
6780       CopyBoard(testBoard, boards[currentMove]);
6781       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6782
6783       if (CompareBoards(testBoard, boards[currentMove+1])) {
6784         ForwardInner(currentMove+1);
6785
6786         /* Autoplay the opponent's response.
6787          * if appData.animate was TRUE when Training mode was entered,
6788          * the response will be animated.
6789          */
6790         saveAnimate = appData.animate;
6791         appData.animate = animateTraining;
6792         ForwardInner(currentMove+1);
6793         appData.animate = saveAnimate;
6794
6795         /* check for the end of the game */
6796         if (currentMove >= forwardMostMove) {
6797           gameMode = PlayFromGameFile;
6798           ModeHighlight();
6799           SetTrainingModeOff();
6800           DisplayInformation(_("End of game"));
6801         }
6802       } else {
6803         DisplayError(_("Incorrect move"), 0);
6804       }
6805       return 1;
6806     }
6807
6808   /* Ok, now we know that the move is good, so we can kill
6809      the previous line in Analysis Mode */
6810   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6811                                 && currentMove < forwardMostMove) {
6812     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6813     else forwardMostMove = currentMove;
6814   }
6815
6816   ClearMap();
6817
6818   /* If we need the chess program but it's dead, restart it */
6819   ResurrectChessProgram();
6820
6821   /* A user move restarts a paused game*/
6822   if (pausing)
6823     PauseEvent();
6824
6825   thinkOutput[0] = NULLCHAR;
6826
6827   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6828
6829   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6830     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6831     return 1;
6832   }
6833
6834   if (gameMode == BeginningOfGame) {
6835     if (appData.noChessProgram) {
6836       gameMode = EditGame;
6837       SetGameInfo();
6838     } else {
6839       char buf[MSG_SIZ];
6840       gameMode = MachinePlaysBlack;
6841       StartClocks();
6842       SetGameInfo();
6843       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6844       DisplayTitle(buf);
6845       if (first.sendName) {
6846         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6847         SendToProgram(buf, &first);
6848       }
6849       StartClocks();
6850     }
6851     ModeHighlight();
6852   }
6853
6854   /* Relay move to ICS or chess engine */
6855   if (appData.icsActive) {
6856     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6857         gameMode == IcsExamining) {
6858       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6859         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6860         SendToICS("draw ");
6861         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6862       }
6863       // also send plain move, in case ICS does not understand atomic claims
6864       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6865       ics_user_moved = 1;
6866     }
6867   } else {
6868     if (first.sendTime && (gameMode == BeginningOfGame ||
6869                            gameMode == MachinePlaysWhite ||
6870                            gameMode == MachinePlaysBlack)) {
6871       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6872     }
6873     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6874          // [HGM] book: if program might be playing, let it use book
6875         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6876         first.maybeThinking = TRUE;
6877     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6878         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6879         SendBoard(&first, currentMove+1);
6880     } else SendMoveToProgram(forwardMostMove-1, &first);
6881     if (currentMove == cmailOldMove + 1) {
6882       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6883     }
6884   }
6885
6886   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887
6888   switch (gameMode) {
6889   case EditGame:
6890     if(appData.testLegality)
6891     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6892     case MT_NONE:
6893     case MT_CHECK:
6894       break;
6895     case MT_CHECKMATE:
6896     case MT_STAINMATE:
6897       if (WhiteOnMove(currentMove)) {
6898         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6899       } else {
6900         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6901       }
6902       break;
6903     case MT_STALEMATE:
6904       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6905       break;
6906     }
6907     break;
6908
6909   case MachinePlaysBlack:
6910   case MachinePlaysWhite:
6911     /* disable certain menu options while machine is thinking */
6912     SetMachineThinkingEnables();
6913     break;
6914
6915   default:
6916     break;
6917   }
6918
6919   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6920   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6921
6922   if(bookHit) { // [HGM] book: simulate book reply
6923         static char bookMove[MSG_SIZ]; // a bit generous?
6924
6925         programStats.nodes = programStats.depth = programStats.time =
6926         programStats.score = programStats.got_only_move = 0;
6927         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6928
6929         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6930         strcat(bookMove, bookHit);
6931         HandleMachineMove(bookMove, &first);
6932   }
6933   return 1;
6934 }
6935
6936 void
6937 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6938 {
6939     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6940     Markers *m = (Markers *) closure;
6941     if(rf == fromY && ff == fromX)
6942         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6943                          || kind == WhiteCapturesEnPassant
6944                          || kind == BlackCapturesEnPassant);
6945     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6946 }
6947
6948 void
6949 MarkTargetSquares (int clear)
6950 {
6951   int x, y;
6952   if(clear) // no reason to ever suppress clearing
6953     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6954   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6955      !appData.testLegality || gameMode == EditPosition) return;
6956   if(!clear) {
6957     int capt = 0;
6958     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6959     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6960       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6961       if(capt)
6962       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6963     }
6964   }
6965   DrawPosition(FALSE, NULL);
6966 }
6967
6968 int
6969 Explode (Board board, int fromX, int fromY, int toX, int toY)
6970 {
6971     if(gameInfo.variant == VariantAtomic &&
6972        (board[toY][toX] != EmptySquare ||                     // capture?
6973         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6974                          board[fromY][fromX] == BlackPawn   )
6975       )) {
6976         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6977         return TRUE;
6978     }
6979     return FALSE;
6980 }
6981
6982 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6983
6984 int
6985 CanPromote (ChessSquare piece, int y)
6986 {
6987         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6988         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6989         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6990            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6991            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6992                                                   gameInfo.variant == VariantMakruk) return FALSE;
6993         return (piece == BlackPawn && y == 1 ||
6994                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6995                 piece == BlackLance && y == 1 ||
6996                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6997 }
6998
6999 void
7000 LeftClick (ClickType clickType, int xPix, int yPix)
7001 {
7002     int x, y;
7003     Boolean saveAnimate;
7004     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7005     char promoChoice = NULLCHAR;
7006     ChessSquare piece;
7007     static TimeMark lastClickTime, prevClickTime;
7008
7009     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7010
7011     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7012
7013     if (clickType == Press) ErrorPopDown();
7014
7015     x = EventToSquare(xPix, BOARD_WIDTH);
7016     y = EventToSquare(yPix, BOARD_HEIGHT);
7017     if (!flipView && y >= 0) {
7018         y = BOARD_HEIGHT - 1 - y;
7019     }
7020     if (flipView && x >= 0) {
7021         x = BOARD_WIDTH - 1 - x;
7022     }
7023
7024     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7025         defaultPromoChoice = promoSweep;
7026         promoSweep = EmptySquare;   // terminate sweep
7027         promoDefaultAltered = TRUE;
7028         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7029     }
7030
7031     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7032         if(clickType == Release) return; // ignore upclick of click-click destination
7033         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7034         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7035         if(gameInfo.holdingsWidth &&
7036                 (WhiteOnMove(currentMove)
7037                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7038                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7039             // click in right holdings, for determining promotion piece
7040             ChessSquare p = boards[currentMove][y][x];
7041             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7042             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7043             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7044                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7045                 fromX = fromY = -1;
7046                 return;
7047             }
7048         }
7049         DrawPosition(FALSE, boards[currentMove]);
7050         return;
7051     }
7052
7053     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7054     if(clickType == Press
7055             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7056               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7057               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7058         return;
7059
7060     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7061         // could be static click on premove from-square: abort premove
7062         gotPremove = 0;
7063         ClearPremoveHighlights();
7064     }
7065
7066     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7067         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7068
7069     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7070         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7071                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7072         defaultPromoChoice = DefaultPromoChoice(side);
7073     }
7074
7075     autoQueen = appData.alwaysPromoteToQueen;
7076
7077     if (fromX == -1) {
7078       int originalY = y;
7079       gatingPiece = EmptySquare;
7080       if (clickType != Press) {
7081         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7082             DragPieceEnd(xPix, yPix); dragging = 0;
7083             DrawPosition(FALSE, NULL);
7084         }
7085         return;
7086       }
7087       doubleClick = FALSE;
7088       fromX = x; fromY = y; toX = toY = -1;
7089       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7090          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7091          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7092             /* First square */
7093             if (OKToStartUserMove(fromX, fromY)) {
7094                 second = 0;
7095                 MarkTargetSquares(0);
7096                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7097                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7098                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7099                     promoSweep = defaultPromoChoice;
7100                     selectFlag = 0; lastX = xPix; lastY = yPix;
7101                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7102                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7103                 }
7104                 if (appData.highlightDragging) {
7105                     SetHighlights(fromX, fromY, -1, -1);
7106                 } else {
7107                     ClearHighlights();
7108                 }
7109             } else fromX = fromY = -1;
7110             return;
7111         }
7112     }
7113
7114     /* fromX != -1 */
7115     if (clickType == Press && gameMode != EditPosition) {
7116         ChessSquare fromP;
7117         ChessSquare toP;
7118         int frc;
7119
7120         // ignore off-board to clicks
7121         if(y < 0 || x < 0) return;
7122
7123         /* Check if clicking again on the same color piece */
7124         fromP = boards[currentMove][fromY][fromX];
7125         toP = boards[currentMove][y][x];
7126         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7127         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7128              WhitePawn <= toP && toP <= WhiteKing &&
7129              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7130              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7131             (BlackPawn <= fromP && fromP <= BlackKing &&
7132              BlackPawn <= toP && toP <= BlackKing &&
7133              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7134              !(fromP == BlackKing && toP == BlackRook && frc))) {
7135             /* Clicked again on same color piece -- changed his mind */
7136             second = (x == fromX && y == fromY);
7137             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7138                 second = FALSE; // first double-click rather than scond click
7139                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7140             }
7141             promoDefaultAltered = FALSE;
7142             MarkTargetSquares(1);
7143            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7144             if (appData.highlightDragging) {
7145                 SetHighlights(x, y, -1, -1);
7146             } else {
7147                 ClearHighlights();
7148             }
7149             if (OKToStartUserMove(x, y)) {
7150                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7151                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7152                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7153                  gatingPiece = boards[currentMove][fromY][fromX];
7154                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7155                 fromX = x;
7156                 fromY = y; dragging = 1;
7157                 MarkTargetSquares(0);
7158                 DragPieceBegin(xPix, yPix, FALSE);
7159                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7160                     promoSweep = defaultPromoChoice;
7161                     selectFlag = 0; lastX = xPix; lastY = yPix;
7162                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7163                 }
7164             }
7165            }
7166            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7167            second = FALSE; 
7168         }
7169         // ignore clicks on holdings
7170         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7171     }
7172
7173     if (clickType == Release && x == fromX && y == fromY) {
7174         DragPieceEnd(xPix, yPix); dragging = 0;
7175         if(clearFlag) {
7176             // a deferred attempt to click-click move an empty square on top of a piece
7177             boards[currentMove][y][x] = EmptySquare;
7178             ClearHighlights();
7179             DrawPosition(FALSE, boards[currentMove]);
7180             fromX = fromY = -1; clearFlag = 0;
7181             return;
7182         }
7183         if (appData.animateDragging) {
7184             /* Undo animation damage if any */
7185             DrawPosition(FALSE, NULL);
7186         }
7187         if (second || sweepSelecting) {
7188             /* Second up/down in same square; just abort move */
7189             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7190             second = sweepSelecting = 0;
7191             fromX = fromY = -1;
7192             gatingPiece = EmptySquare;
7193             ClearHighlights();
7194             gotPremove = 0;
7195             ClearPremoveHighlights();
7196         } else {
7197             /* First upclick in same square; start click-click mode */
7198             SetHighlights(x, y, -1, -1);
7199         }
7200         return;
7201     }
7202
7203     clearFlag = 0;
7204
7205     /* we now have a different from- and (possibly off-board) to-square */
7206     /* Completed move */
7207     if(!sweepSelecting) {
7208         toX = x;
7209         toY = y;
7210     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7211
7212     saveAnimate = appData.animate;
7213     if (clickType == Press) {
7214         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7215             // must be Edit Position mode with empty-square selected
7216             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7217             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7218             return;
7219         }
7220         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7221           if(appData.sweepSelect) {
7222             ChessSquare piece = boards[currentMove][fromY][fromX];
7223             promoSweep = defaultPromoChoice;
7224             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7225             selectFlag = 0; lastX = xPix; lastY = yPix;
7226             Sweep(0); // Pawn that is going to promote: preview promotion piece
7227             sweepSelecting = 1;
7228             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7229             MarkTargetSquares(1);
7230           }
7231           return; // promo popup appears on up-click
7232         }
7233         /* Finish clickclick move */
7234         if (appData.animate || appData.highlightLastMove) {
7235             SetHighlights(fromX, fromY, toX, toY);
7236         } else {
7237             ClearHighlights();
7238         }
7239     } else {
7240         /* Finish drag move */
7241         if (appData.highlightLastMove) {
7242             SetHighlights(fromX, fromY, toX, toY);
7243         } else {
7244             ClearHighlights();
7245         }
7246         DragPieceEnd(xPix, yPix); dragging = 0;
7247         /* Don't animate move and drag both */
7248         appData.animate = FALSE;
7249     }
7250     MarkTargetSquares(1);
7251
7252     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7253     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7254         ChessSquare piece = boards[currentMove][fromY][fromX];
7255         if(gameMode == EditPosition && piece != EmptySquare &&
7256            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7257             int n;
7258
7259             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7260                 n = PieceToNumber(piece - (int)BlackPawn);
7261                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7262                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7263                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7264             } else
7265             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7266                 n = PieceToNumber(piece);
7267                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7268                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7269                 boards[currentMove][n][BOARD_WIDTH-2]++;
7270             }
7271             boards[currentMove][fromY][fromX] = EmptySquare;
7272         }
7273         ClearHighlights();
7274         fromX = fromY = -1;
7275         DrawPosition(TRUE, boards[currentMove]);
7276         return;
7277     }
7278
7279     // off-board moves should not be highlighted
7280     if(x < 0 || y < 0) ClearHighlights();
7281
7282     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7283
7284     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7285         SetHighlights(fromX, fromY, toX, toY);
7286         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7287             // [HGM] super: promotion to captured piece selected from holdings
7288             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7289             promotionChoice = TRUE;
7290             // kludge follows to temporarily execute move on display, without promoting yet
7291             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7292             boards[currentMove][toY][toX] = p;
7293             DrawPosition(FALSE, boards[currentMove]);
7294             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7295             boards[currentMove][toY][toX] = q;
7296             DisplayMessage("Click in holdings to choose piece", "");
7297             return;
7298         }
7299         PromotionPopUp();
7300     } else {
7301         int oldMove = currentMove;
7302         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7303         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7304         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7305         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7306            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7307             DrawPosition(TRUE, boards[currentMove]);
7308         fromX = fromY = -1;
7309     }
7310     appData.animate = saveAnimate;
7311     if (appData.animate || appData.animateDragging) {
7312         /* Undo animation damage if needed */
7313         DrawPosition(FALSE, NULL);
7314     }
7315 }
7316
7317 int
7318 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7319 {   // front-end-free part taken out of PieceMenuPopup
7320     int whichMenu; int xSqr, ySqr;
7321
7322     if(seekGraphUp) { // [HGM] seekgraph
7323         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7324         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7325         return -2;
7326     }
7327
7328     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7329          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7330         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7331         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7332         if(action == Press)   {
7333             originalFlip = flipView;
7334             flipView = !flipView; // temporarily flip board to see game from partners perspective
7335             DrawPosition(TRUE, partnerBoard);
7336             DisplayMessage(partnerStatus, "");
7337             partnerUp = TRUE;
7338         } else if(action == Release) {
7339             flipView = originalFlip;
7340             DrawPosition(TRUE, boards[currentMove]);
7341             partnerUp = FALSE;
7342         }
7343         return -2;
7344     }
7345
7346     xSqr = EventToSquare(x, BOARD_WIDTH);
7347     ySqr = EventToSquare(y, BOARD_HEIGHT);
7348     if (action == Release) {
7349         if(pieceSweep != EmptySquare) {
7350             EditPositionMenuEvent(pieceSweep, toX, toY);
7351             pieceSweep = EmptySquare;
7352         } else UnLoadPV(); // [HGM] pv
7353     }
7354     if (action != Press) return -2; // return code to be ignored
7355     switch (gameMode) {
7356       case IcsExamining:
7357         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7358       case EditPosition:
7359         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7360         if (xSqr < 0 || ySqr < 0) return -1;
7361         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7362         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7363         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7364         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7365         NextPiece(0);
7366         return 2; // grab
7367       case IcsObserving:
7368         if(!appData.icsEngineAnalyze) return -1;
7369       case IcsPlayingWhite:
7370       case IcsPlayingBlack:
7371         if(!appData.zippyPlay) goto noZip;
7372       case AnalyzeMode:
7373       case AnalyzeFile:
7374       case MachinePlaysWhite:
7375       case MachinePlaysBlack:
7376       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7377         if (!appData.dropMenu) {
7378           LoadPV(x, y);
7379           return 2; // flag front-end to grab mouse events
7380         }
7381         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7382            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7383       case EditGame:
7384       noZip:
7385         if (xSqr < 0 || ySqr < 0) return -1;
7386         if (!appData.dropMenu || appData.testLegality &&
7387             gameInfo.variant != VariantBughouse &&
7388             gameInfo.variant != VariantCrazyhouse) return -1;
7389         whichMenu = 1; // drop menu
7390         break;
7391       default:
7392         return -1;
7393     }
7394
7395     if (((*fromX = xSqr) < 0) ||
7396         ((*fromY = ySqr) < 0)) {
7397         *fromX = *fromY = -1;
7398         return -1;
7399     }
7400     if (flipView)
7401       *fromX = BOARD_WIDTH - 1 - *fromX;
7402     else
7403       *fromY = BOARD_HEIGHT - 1 - *fromY;
7404
7405     return whichMenu;
7406 }
7407
7408 void
7409 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7410 {
7411 //    char * hint = lastHint;
7412     FrontEndProgramStats stats;
7413
7414     stats.which = cps == &first ? 0 : 1;
7415     stats.depth = cpstats->depth;
7416     stats.nodes = cpstats->nodes;
7417     stats.score = cpstats->score;
7418     stats.time = cpstats->time;
7419     stats.pv = cpstats->movelist;
7420     stats.hint = lastHint;
7421     stats.an_move_index = 0;
7422     stats.an_move_count = 0;
7423
7424     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7425         stats.hint = cpstats->move_name;
7426         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7427         stats.an_move_count = cpstats->nr_moves;
7428     }
7429
7430     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
7431
7432     SetProgramStats( &stats );
7433 }
7434
7435 void
7436 ClearEngineOutputPane (int which)
7437 {
7438     static FrontEndProgramStats dummyStats;
7439     dummyStats.which = which;
7440     dummyStats.pv = "#";
7441     SetProgramStats( &dummyStats );
7442 }
7443
7444 #define MAXPLAYERS 500
7445
7446 char *
7447 TourneyStandings (int display)
7448 {
7449     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7450     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7451     char result, *p, *names[MAXPLAYERS];
7452
7453     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7454         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7455     names[0] = p = strdup(appData.participants);
7456     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7457
7458     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7459
7460     while(result = appData.results[nr]) {
7461         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7462         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7463         wScore = bScore = 0;
7464         switch(result) {
7465           case '+': wScore = 2; break;
7466           case '-': bScore = 2; break;
7467           case '=': wScore = bScore = 1; break;
7468           case ' ':
7469           case '*': return strdup("busy"); // tourney not finished
7470         }
7471         score[w] += wScore;
7472         score[b] += bScore;
7473         games[w]++;
7474         games[b]++;
7475         nr++;
7476     }
7477     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7478     for(w=0; w<nPlayers; w++) {
7479         bScore = -1;
7480         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7481         ranking[w] = b; points[w] = bScore; score[b] = -2;
7482     }
7483     p = malloc(nPlayers*34+1);
7484     for(w=0; w<nPlayers && w<display; w++)
7485         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7486     free(names[0]);
7487     return p;
7488 }
7489
7490 void
7491 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7492 {       // count all piece types
7493         int p, f, r;
7494         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7495         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7496         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7497                 p = board[r][f];
7498                 pCnt[p]++;
7499                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7500                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7501                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7502                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7503                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7504                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7505         }
7506 }
7507
7508 int
7509 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7510 {
7511         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7512         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7513
7514         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7515         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7516         if(myPawns == 2 && nMine == 3) // KPP
7517             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7518         if(myPawns == 1 && nMine == 2) // KP
7519             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7520         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7521             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7522         if(myPawns) return FALSE;
7523         if(pCnt[WhiteRook+side])
7524             return pCnt[BlackRook-side] ||
7525                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7526                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7527                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7528         if(pCnt[WhiteCannon+side]) {
7529             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7530             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7531         }
7532         if(pCnt[WhiteKnight+side])
7533             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7534         return FALSE;
7535 }
7536
7537 int
7538 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7539 {
7540         VariantClass v = gameInfo.variant;
7541
7542         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7543         if(v == VariantShatranj) return TRUE; // always winnable through baring
7544         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7545         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7546
7547         if(v == VariantXiangqi) {
7548                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7549
7550                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7551                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7552                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7553                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7554                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7555                 if(stale) // we have at least one last-rank P plus perhaps C
7556                     return majors // KPKX
7557                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7558                 else // KCA*E*
7559                     return pCnt[WhiteFerz+side] // KCAK
7560                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7561                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7562                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7563
7564         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7565                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7566
7567                 if(nMine == 1) return FALSE; // bare King
7568                 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
7569                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7570                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7571                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7572                 if(pCnt[WhiteKnight+side])
7573                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7574                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7575                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7576                 if(nBishops)
7577                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7578                 if(pCnt[WhiteAlfil+side])
7579                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7580                 if(pCnt[WhiteWazir+side])
7581                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7582         }
7583
7584         return TRUE;
7585 }
7586
7587 int
7588 CompareWithRights (Board b1, Board b2)
7589 {
7590     int rights = 0;
7591     if(!CompareBoards(b1, b2)) return FALSE;
7592     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7593     /* compare castling rights */
7594     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7595            rights++; /* King lost rights, while rook still had them */
7596     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7597         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7598            rights++; /* but at least one rook lost them */
7599     }
7600     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7601            rights++;
7602     if( b1[CASTLING][5] != NoRights ) {
7603         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7604            rights++;
7605     }
7606     return rights == 0;
7607 }
7608
7609 int
7610 Adjudicate (ChessProgramState *cps)
7611 {       // [HGM] some adjudications useful with buggy engines
7612         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7613         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7614         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7615         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7616         int k, count = 0; static int bare = 1;
7617         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7618         Boolean canAdjudicate = !appData.icsActive;
7619
7620         // most tests only when we understand the game, i.e. legality-checking on
7621             if( appData.testLegality )
7622             {   /* [HGM] Some more adjudications for obstinate engines */
7623                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7624                 static int moveCount = 6;
7625                 ChessMove result;
7626                 char *reason = NULL;
7627
7628                 /* Count what is on board. */
7629                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7630
7631                 /* Some material-based adjudications that have to be made before stalemate test */
7632                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7633                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7634                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7635                      if(canAdjudicate && appData.checkMates) {
7636                          if(engineOpponent)
7637                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7638                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7639                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7640                          return 1;
7641                      }
7642                 }
7643
7644                 /* Bare King in Shatranj (loses) or Losers (wins) */
7645                 if( nrW == 1 || nrB == 1) {
7646                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7647                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7648                      if(canAdjudicate && appData.checkMates) {
7649                          if(engineOpponent)
7650                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7651                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7652                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7653                          return 1;
7654                      }
7655                   } else
7656                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7657                   {    /* bare King */
7658                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7659                         if(canAdjudicate && appData.checkMates) {
7660                             /* but only adjudicate if adjudication enabled */
7661                             if(engineOpponent)
7662                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7663                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7664                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7665                             return 1;
7666                         }
7667                   }
7668                 } else bare = 1;
7669
7670
7671             // don't wait for engine to announce game end if we can judge ourselves
7672             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7673               case MT_CHECK:
7674                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7675                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7676                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7677                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7678                             checkCnt++;
7679                         if(checkCnt >= 2) {
7680                             reason = "Xboard adjudication: 3rd check";
7681                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7682                             break;
7683                         }
7684                     }
7685                 }
7686               case MT_NONE:
7687               default:
7688                 break;
7689               case MT_STALEMATE:
7690               case MT_STAINMATE:
7691                 reason = "Xboard adjudication: Stalemate";
7692                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7693                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7694                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7695                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7696                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7697                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7698                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7699                                                                         EP_CHECKMATE : EP_WINS);
7700                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7701                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7702                 }
7703                 break;
7704               case MT_CHECKMATE:
7705                 reason = "Xboard adjudication: Checkmate";
7706                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7707                 break;
7708             }
7709
7710                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7711                     case EP_STALEMATE:
7712                         result = GameIsDrawn; break;
7713                     case EP_CHECKMATE:
7714                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7715                     case EP_WINS:
7716                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7717                     default:
7718                         result = EndOfFile;
7719                 }
7720                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7721                     if(engineOpponent)
7722                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7723                     GameEnds( result, reason, GE_XBOARD );
7724                     return 1;
7725                 }
7726
7727                 /* Next absolutely insufficient mating material. */
7728                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7729                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7730                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7731
7732                      /* always flag draws, for judging claims */
7733                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7734
7735                      if(canAdjudicate && appData.materialDraws) {
7736                          /* but only adjudicate them if adjudication enabled */
7737                          if(engineOpponent) {
7738                            SendToProgram("force\n", engineOpponent); // suppress reply
7739                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7740                          }
7741                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7742                          return 1;
7743                      }
7744                 }
7745
7746                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7747                 if(gameInfo.variant == VariantXiangqi ?
7748                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7749                  : nrW + nrB == 4 &&
7750                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7751                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7752                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7753                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7754                    ) ) {
7755                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7756                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7757                           if(engineOpponent) {
7758                             SendToProgram("force\n", engineOpponent); // suppress reply
7759                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7760                           }
7761                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7762                           return 1;
7763                      }
7764                 } else moveCount = 6;
7765             }
7766
7767         // Repetition draws and 50-move rule can be applied independently of legality testing
7768
7769                 /* Check for rep-draws */
7770                 count = 0;
7771                 for(k = forwardMostMove-2;
7772                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7773                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7774                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7775                     k-=2)
7776                 {   int rights=0;
7777                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7778                         /* compare castling rights */
7779                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7780                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7781                                 rights++; /* King lost rights, while rook still had them */
7782                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7783                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7784                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7785                                    rights++; /* but at least one rook lost them */
7786                         }
7787                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7788                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7789                                 rights++;
7790                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7791                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7792                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7793                                    rights++;
7794                         }
7795                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7796                             && appData.drawRepeats > 1) {
7797                              /* adjudicate after user-specified nr of repeats */
7798                              int result = GameIsDrawn;
7799                              char *details = "XBoard adjudication: repetition draw";
7800                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7801                                 // [HGM] xiangqi: check for forbidden perpetuals
7802                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7803                                 for(m=forwardMostMove; m>k; m-=2) {
7804                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7805                                         ourPerpetual = 0; // the current mover did not always check
7806                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7807                                         hisPerpetual = 0; // the opponent did not always check
7808                                 }
7809                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7810                                                                         ourPerpetual, hisPerpetual);
7811                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7812                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7813                                     details = "Xboard adjudication: perpetual checking";
7814                                 } else
7815                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7816                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7817                                 } else
7818                                 // Now check for perpetual chases
7819                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7820                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7821                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7822                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7823                                         static char resdet[MSG_SIZ];
7824                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7825                                         details = resdet;
7826                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7827                                     } else
7828                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7829                                         break; // Abort repetition-checking loop.
7830                                 }
7831                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7832                              }
7833                              if(engineOpponent) {
7834                                SendToProgram("force\n", engineOpponent); // suppress reply
7835                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7836                              }
7837                              GameEnds( result, details, GE_XBOARD );
7838                              return 1;
7839                         }
7840                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7841                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7842                     }
7843                 }
7844
7845                 /* Now we test for 50-move draws. Determine ply count */
7846                 count = forwardMostMove;
7847                 /* look for last irreversble move */
7848                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7849                     count--;
7850                 /* if we hit starting position, add initial plies */
7851                 if( count == backwardMostMove )
7852                     count -= initialRulePlies;
7853                 count = forwardMostMove - count;
7854                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7855                         // adjust reversible move counter for checks in Xiangqi
7856                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7857                         if(i < backwardMostMove) i = backwardMostMove;
7858                         while(i <= forwardMostMove) {
7859                                 lastCheck = inCheck; // check evasion does not count
7860                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7861                                 if(inCheck || lastCheck) count--; // check does not count
7862                                 i++;
7863                         }
7864                 }
7865                 if( count >= 100)
7866                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7867                          /* this is used to judge if draw claims are legal */
7868                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7869                          if(engineOpponent) {
7870                            SendToProgram("force\n", engineOpponent); // suppress reply
7871                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7872                          }
7873                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7874                          return 1;
7875                 }
7876
7877                 /* if draw offer is pending, treat it as a draw claim
7878                  * when draw condition present, to allow engines a way to
7879                  * claim draws before making their move to avoid a race
7880                  * condition occurring after their move
7881                  */
7882                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7883                          char *p = NULL;
7884                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7885                              p = "Draw claim: 50-move rule";
7886                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7887                              p = "Draw claim: 3-fold repetition";
7888                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7889                              p = "Draw claim: insufficient mating material";
7890                          if( p != NULL && canAdjudicate) {
7891                              if(engineOpponent) {
7892                                SendToProgram("force\n", engineOpponent); // suppress reply
7893                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7894                              }
7895                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7896                              return 1;
7897                          }
7898                 }
7899
7900                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7901                     if(engineOpponent) {
7902                       SendToProgram("force\n", engineOpponent); // suppress reply
7903                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7904                     }
7905                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7906                     return 1;
7907                 }
7908         return 0;
7909 }
7910
7911 char *
7912 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7913 {   // [HGM] book: this routine intercepts moves to simulate book replies
7914     char *bookHit = NULL;
7915
7916     //first determine if the incoming move brings opponent into his book
7917     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7918         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7919     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7920     if(bookHit != NULL && !cps->bookSuspend) {
7921         // make sure opponent is not going to reply after receiving move to book position
7922         SendToProgram("force\n", cps);
7923         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7924     }
7925     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7926     // now arrange restart after book miss
7927     if(bookHit) {
7928         // after a book hit we never send 'go', and the code after the call to this routine
7929         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7930         char buf[MSG_SIZ], *move = bookHit;
7931         if(cps->useSAN) {
7932             int fromX, fromY, toX, toY;
7933             char promoChar;
7934             ChessMove moveType;
7935             move = buf + 30;
7936             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7937                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7938                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7939                                     PosFlags(forwardMostMove),
7940                                     fromY, fromX, toY, toX, promoChar, move);
7941             } else {
7942                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7943                 bookHit = NULL;
7944             }
7945         }
7946         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7947         SendToProgram(buf, cps);
7948         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7949     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7950         SendToProgram("go\n", cps);
7951         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7952     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7953         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7954             SendToProgram("go\n", cps);
7955         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7956     }
7957     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7958 }
7959
7960 int
7961 LoadError (char *errmess, ChessProgramState *cps)
7962 {   // unloads engine and switches back to -ncp mode if it was first
7963     if(cps->initDone) return FALSE;
7964     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7965     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7966     cps->pr = NoProc; 
7967     if(cps == &first) {
7968         appData.noChessProgram = TRUE;
7969         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7970         gameMode = BeginningOfGame; ModeHighlight();
7971         SetNCPMode();
7972     }
7973     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7974     DisplayMessage("", ""); // erase waiting message
7975     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7976     return TRUE;
7977 }
7978
7979 char *savedMessage;
7980 ChessProgramState *savedState;
7981 void
7982 DeferredBookMove (void)
7983 {
7984         if(savedState->lastPing != savedState->lastPong)
7985                     ScheduleDelayedEvent(DeferredBookMove, 10);
7986         else
7987         HandleMachineMove(savedMessage, savedState);
7988 }
7989
7990 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7991
7992 void
7993 HandleMachineMove (char *message, ChessProgramState *cps)
7994 {
7995     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7996     char realname[MSG_SIZ];
7997     int fromX, fromY, toX, toY;
7998     ChessMove moveType;
7999     char promoChar;
8000     char *p, *pv=buf1;
8001     int machineWhite, oldError;
8002     char *bookHit;
8003
8004     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8005         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8006         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8007             DisplayError(_("Invalid pairing from pairing engine"), 0);
8008             return;
8009         }
8010         pairingReceived = 1;
8011         NextMatchGame();
8012         return; // Skim the pairing messages here.
8013     }
8014
8015     oldError = cps->userError; cps->userError = 0;
8016
8017 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8018     /*
8019      * Kludge to ignore BEL characters
8020      */
8021     while (*message == '\007') message++;
8022
8023     /*
8024      * [HGM] engine debug message: ignore lines starting with '#' character
8025      */
8026     if(cps->debug && *message == '#') return;
8027
8028     /*
8029      * Look for book output
8030      */
8031     if (cps == &first && bookRequested) {
8032         if (message[0] == '\t' || message[0] == ' ') {
8033             /* Part of the book output is here; append it */
8034             strcat(bookOutput, message);
8035             strcat(bookOutput, "  \n");
8036             return;
8037         } else if (bookOutput[0] != NULLCHAR) {
8038             /* All of book output has arrived; display it */
8039             char *p = bookOutput;
8040             while (*p != NULLCHAR) {
8041                 if (*p == '\t') *p = ' ';
8042                 p++;
8043             }
8044             DisplayInformation(bookOutput);
8045             bookRequested = FALSE;
8046             /* Fall through to parse the current output */
8047         }
8048     }
8049
8050     /*
8051      * Look for machine move.
8052      */
8053     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8054         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8055     {
8056         /* This method is only useful on engines that support ping */
8057         if (cps->lastPing != cps->lastPong) {
8058           if (gameMode == BeginningOfGame) {
8059             /* Extra move from before last new; ignore */
8060             if (appData.debugMode) {
8061                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8062             }
8063           } else {
8064             if (appData.debugMode) {
8065                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8066                         cps->which, gameMode);
8067             }
8068
8069             SendToProgram("undo\n", cps);
8070           }
8071           return;
8072         }
8073
8074         switch (gameMode) {
8075           case BeginningOfGame:
8076             /* Extra move from before last reset; ignore */
8077             if (appData.debugMode) {
8078                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8079             }
8080             return;
8081
8082           case EndOfGame:
8083           case IcsIdle:
8084           default:
8085             /* Extra move after we tried to stop.  The mode test is
8086                not a reliable way of detecting this problem, but it's
8087                the best we can do on engines that don't support ping.
8088             */
8089             if (appData.debugMode) {
8090                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8091                         cps->which, gameMode);
8092             }
8093             SendToProgram("undo\n", cps);
8094             return;
8095
8096           case MachinePlaysWhite:
8097           case IcsPlayingWhite:
8098             machineWhite = TRUE;
8099             break;
8100
8101           case MachinePlaysBlack:
8102           case IcsPlayingBlack:
8103             machineWhite = FALSE;
8104             break;
8105
8106           case TwoMachinesPlay:
8107             machineWhite = (cps->twoMachinesColor[0] == 'w');
8108             break;
8109         }
8110         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8111             if (appData.debugMode) {
8112                 fprintf(debugFP,
8113                         "Ignoring move out of turn by %s, gameMode %d"
8114                         ", forwardMost %d\n",
8115                         cps->which, gameMode, forwardMostMove);
8116             }
8117             return;
8118         }
8119
8120         if(cps->alphaRank) AlphaRank(machineMove, 4);
8121         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8122                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8123             /* Machine move could not be parsed; ignore it. */
8124           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8125                     machineMove, _(cps->which));
8126             DisplayError(buf1, 0);
8127             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8128                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8129             if (gameMode == TwoMachinesPlay) {
8130               GameEnds(machineWhite ? BlackWins : WhiteWins,
8131                        buf1, GE_XBOARD);
8132             }
8133             return;
8134         }
8135
8136         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8137         /* So we have to redo legality test with true e.p. status here,  */
8138         /* to make sure an illegal e.p. capture does not slip through,   */
8139         /* to cause a forfeit on a justified illegal-move complaint      */
8140         /* of the opponent.                                              */
8141         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8142            ChessMove moveType;
8143            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8144                              fromY, fromX, toY, toX, promoChar);
8145             if(moveType == IllegalMove) {
8146               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8147                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8148                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8149                            buf1, GE_XBOARD);
8150                 return;
8151            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8152            /* [HGM] Kludge to handle engines that send FRC-style castling
8153               when they shouldn't (like TSCP-Gothic) */
8154            switch(moveType) {
8155              case WhiteASideCastleFR:
8156              case BlackASideCastleFR:
8157                toX+=2;
8158                currentMoveString[2]++;
8159                break;
8160              case WhiteHSideCastleFR:
8161              case BlackHSideCastleFR:
8162                toX--;
8163                currentMoveString[2]--;
8164                break;
8165              default: ; // nothing to do, but suppresses warning of pedantic compilers
8166            }
8167         }
8168         hintRequested = FALSE;
8169         lastHint[0] = NULLCHAR;
8170         bookRequested = FALSE;
8171         /* Program may be pondering now */
8172         cps->maybeThinking = TRUE;
8173         if (cps->sendTime == 2) cps->sendTime = 1;
8174         if (cps->offeredDraw) cps->offeredDraw--;
8175
8176         /* [AS] Save move info*/
8177         pvInfoList[ forwardMostMove ].score = programStats.score;
8178         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8179         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8180
8181         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8182
8183         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8184         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8185             int count = 0;
8186
8187             while( count < adjudicateLossPlies ) {
8188                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8189
8190                 if( count & 1 ) {
8191                     score = -score; /* Flip score for winning side */
8192                 }
8193
8194                 if( score > adjudicateLossThreshold ) {
8195                     break;
8196                 }
8197
8198                 count++;
8199             }
8200
8201             if( count >= adjudicateLossPlies ) {
8202                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8203
8204                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8205                     "Xboard adjudication",
8206                     GE_XBOARD );
8207
8208                 return;
8209             }
8210         }
8211
8212         if(Adjudicate(cps)) {
8213             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8214             return; // [HGM] adjudicate: for all automatic game ends
8215         }
8216
8217 #if ZIPPY
8218         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8219             first.initDone) {
8220           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8221                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8222                 SendToICS("draw ");
8223                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8224           }
8225           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8226           ics_user_moved = 1;
8227           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8228                 char buf[3*MSG_SIZ];
8229
8230                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8231                         programStats.score / 100.,
8232                         programStats.depth,
8233                         programStats.time / 100.,
8234                         (unsigned int)programStats.nodes,
8235                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8236                         programStats.movelist);
8237                 SendToICS(buf);
8238 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8239           }
8240         }
8241 #endif
8242
8243         /* [AS] Clear stats for next move */
8244         ClearProgramStats();
8245         thinkOutput[0] = NULLCHAR;
8246         hiddenThinkOutputState = 0;
8247
8248         bookHit = NULL;
8249         if (gameMode == TwoMachinesPlay) {
8250             /* [HGM] relaying draw offers moved to after reception of move */
8251             /* and interpreting offer as claim if it brings draw condition */
8252             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8253                 SendToProgram("draw\n", cps->other);
8254             }
8255             if (cps->other->sendTime) {
8256                 SendTimeRemaining(cps->other,
8257                                   cps->other->twoMachinesColor[0] == 'w');
8258             }
8259             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8260             if (firstMove && !bookHit) {
8261                 firstMove = FALSE;
8262                 if (cps->other->useColors) {
8263                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8264                 }
8265                 SendToProgram("go\n", cps->other);
8266             }
8267             cps->other->maybeThinking = TRUE;
8268         }
8269
8270         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8271
8272         if (!pausing && appData.ringBellAfterMoves) {
8273             RingBell();
8274         }
8275
8276         /*
8277          * Reenable menu items that were disabled while
8278          * machine was thinking
8279          */
8280         if (gameMode != TwoMachinesPlay)
8281             SetUserThinkingEnables();
8282
8283         // [HGM] book: after book hit opponent has received move and is now in force mode
8284         // force the book reply into it, and then fake that it outputted this move by jumping
8285         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8286         if(bookHit) {
8287                 static char bookMove[MSG_SIZ]; // a bit generous?
8288
8289                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8290                 strcat(bookMove, bookHit);
8291                 message = bookMove;
8292                 cps = cps->other;
8293                 programStats.nodes = programStats.depth = programStats.time =
8294                 programStats.score = programStats.got_only_move = 0;
8295                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8296
8297                 if(cps->lastPing != cps->lastPong) {
8298                     savedMessage = message; // args for deferred call
8299                     savedState = cps;
8300                     ScheduleDelayedEvent(DeferredBookMove, 10);
8301                     return;
8302                 }
8303                 goto FakeBookMove;
8304         }
8305
8306         return;
8307     }
8308
8309     /* Set special modes for chess engines.  Later something general
8310      *  could be added here; for now there is just one kludge feature,
8311      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8312      *  when "xboard" is given as an interactive command.
8313      */
8314     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8315         cps->useSigint = FALSE;
8316         cps->useSigterm = FALSE;
8317     }
8318     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8319       ParseFeatures(message+8, cps);
8320       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8321     }
8322
8323     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8324                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8325       int dummy, s=6; char buf[MSG_SIZ];
8326       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8327       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8328       if(startedFromSetupPosition) return;
8329       ParseFEN(boards[0], &dummy, message+s);
8330       DrawPosition(TRUE, boards[0]);
8331       startedFromSetupPosition = TRUE;
8332       return;
8333     }
8334     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8335      * want this, I was asked to put it in, and obliged.
8336      */
8337     if (!strncmp(message, "setboard ", 9)) {
8338         Board initial_position;
8339
8340         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8341
8342         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8343             DisplayError(_("Bad FEN received from engine"), 0);
8344             return ;
8345         } else {
8346            Reset(TRUE, FALSE);
8347            CopyBoard(boards[0], initial_position);
8348            initialRulePlies = FENrulePlies;
8349            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8350            else gameMode = MachinePlaysBlack;
8351            DrawPosition(FALSE, boards[currentMove]);
8352         }
8353         return;
8354     }
8355
8356     /*
8357      * Look for communication commands
8358      */
8359     if (!strncmp(message, "telluser ", 9)) {
8360         if(message[9] == '\\' && message[10] == '\\')
8361             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8362         PlayTellSound();
8363         DisplayNote(message + 9);
8364         return;
8365     }
8366     if (!strncmp(message, "tellusererror ", 14)) {
8367         cps->userError = 1;
8368         if(message[14] == '\\' && message[15] == '\\')
8369             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8370         PlayTellSound();
8371         DisplayError(message + 14, 0);
8372         return;
8373     }
8374     if (!strncmp(message, "tellopponent ", 13)) {
8375       if (appData.icsActive) {
8376         if (loggedOn) {
8377           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8378           SendToICS(buf1);
8379         }
8380       } else {
8381         DisplayNote(message + 13);
8382       }
8383       return;
8384     }
8385     if (!strncmp(message, "tellothers ", 11)) {
8386       if (appData.icsActive) {
8387         if (loggedOn) {
8388           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8389           SendToICS(buf1);
8390         }
8391       }
8392       return;
8393     }
8394     if (!strncmp(message, "tellall ", 8)) {
8395       if (appData.icsActive) {
8396         if (loggedOn) {
8397           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8398           SendToICS(buf1);
8399         }
8400       } else {
8401         DisplayNote(message + 8);
8402       }
8403       return;
8404     }
8405     if (strncmp(message, "warning", 7) == 0) {
8406         /* Undocumented feature, use tellusererror in new code */
8407         DisplayError(message, 0);
8408         return;
8409     }
8410     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8411         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8412         strcat(realname, " query");
8413         AskQuestion(realname, buf2, buf1, cps->pr);
8414         return;
8415     }
8416     /* Commands from the engine directly to ICS.  We don't allow these to be
8417      *  sent until we are logged on. Crafty kibitzes have been known to
8418      *  interfere with the login process.
8419      */
8420     if (loggedOn) {
8421         if (!strncmp(message, "tellics ", 8)) {
8422             SendToICS(message + 8);
8423             SendToICS("\n");
8424             return;
8425         }
8426         if (!strncmp(message, "tellicsnoalias ", 15)) {
8427             SendToICS(ics_prefix);
8428             SendToICS(message + 15);
8429             SendToICS("\n");
8430             return;
8431         }
8432         /* The following are for backward compatibility only */
8433         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8434             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8435             SendToICS(ics_prefix);
8436             SendToICS(message);
8437             SendToICS("\n");
8438             return;
8439         }
8440     }
8441     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8442         return;
8443     }
8444     /*
8445      * If the move is illegal, cancel it and redraw the board.
8446      * Also deal with other error cases.  Matching is rather loose
8447      * here to accommodate engines written before the spec.
8448      */
8449     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8450         strncmp(message, "Error", 5) == 0) {
8451         if (StrStr(message, "name") ||
8452             StrStr(message, "rating") || StrStr(message, "?") ||
8453             StrStr(message, "result") || StrStr(message, "board") ||
8454             StrStr(message, "bk") || StrStr(message, "computer") ||
8455             StrStr(message, "variant") || StrStr(message, "hint") ||
8456             StrStr(message, "random") || StrStr(message, "depth") ||
8457             StrStr(message, "accepted")) {
8458             return;
8459         }
8460         if (StrStr(message, "protover")) {
8461           /* Program is responding to input, so it's apparently done
8462              initializing, and this error message indicates it is
8463              protocol version 1.  So we don't need to wait any longer
8464              for it to initialize and send feature commands. */
8465           FeatureDone(cps, 1);
8466           cps->protocolVersion = 1;
8467           return;
8468         }
8469         cps->maybeThinking = FALSE;
8470
8471         if (StrStr(message, "draw")) {
8472             /* Program doesn't have "draw" command */
8473             cps->sendDrawOffers = 0;
8474             return;
8475         }
8476         if (cps->sendTime != 1 &&
8477             (StrStr(message, "time") || StrStr(message, "otim"))) {
8478           /* Program apparently doesn't have "time" or "otim" command */
8479           cps->sendTime = 0;
8480           return;
8481         }
8482         if (StrStr(message, "analyze")) {
8483             cps->analysisSupport = FALSE;
8484             cps->analyzing = FALSE;
8485 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8486             EditGameEvent(); // [HGM] try to preserve loaded game
8487             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8488             DisplayError(buf2, 0);
8489             return;
8490         }
8491         if (StrStr(message, "(no matching move)st")) {
8492           /* Special kludge for GNU Chess 4 only */
8493           cps->stKludge = TRUE;
8494           SendTimeControl(cps, movesPerSession, timeControl,
8495                           timeIncrement, appData.searchDepth,
8496                           searchTime);
8497           return;
8498         }
8499         if (StrStr(message, "(no matching move)sd")) {
8500           /* Special kludge for GNU Chess 4 only */
8501           cps->sdKludge = TRUE;
8502           SendTimeControl(cps, movesPerSession, timeControl,
8503                           timeIncrement, appData.searchDepth,
8504                           searchTime);
8505           return;
8506         }
8507         if (!StrStr(message, "llegal")) {
8508             return;
8509         }
8510         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8511             gameMode == IcsIdle) return;
8512         if (forwardMostMove <= backwardMostMove) return;
8513         if (pausing) PauseEvent();
8514       if(appData.forceIllegal) {
8515             // [HGM] illegal: machine refused move; force position after move into it
8516           SendToProgram("force\n", cps);
8517           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8518                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8519                 // when black is to move, while there might be nothing on a2 or black
8520                 // might already have the move. So send the board as if white has the move.
8521                 // But first we must change the stm of the engine, as it refused the last move
8522                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8523                 if(WhiteOnMove(forwardMostMove)) {
8524                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8525                     SendBoard(cps, forwardMostMove); // kludgeless board
8526                 } else {
8527                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8528                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8529                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8530                 }
8531           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8532             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8533                  gameMode == TwoMachinesPlay)
8534               SendToProgram("go\n", cps);
8535             return;
8536       } else
8537         if (gameMode == PlayFromGameFile) {
8538             /* Stop reading this game file */
8539             gameMode = EditGame;
8540             ModeHighlight();
8541         }
8542         /* [HGM] illegal-move claim should forfeit game when Xboard */
8543         /* only passes fully legal moves                            */
8544         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8545             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8546                                 "False illegal-move claim", GE_XBOARD );
8547             return; // do not take back move we tested as valid
8548         }
8549         currentMove = forwardMostMove-1;
8550         DisplayMove(currentMove-1); /* before DisplayMoveError */
8551         SwitchClocks(forwardMostMove-1); // [HGM] race
8552         DisplayBothClocks();
8553         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8554                 parseList[currentMove], _(cps->which));
8555         DisplayMoveError(buf1);
8556         DrawPosition(FALSE, boards[currentMove]);
8557
8558         SetUserThinkingEnables();
8559         return;
8560     }
8561     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8562         /* Program has a broken "time" command that
8563            outputs a string not ending in newline.
8564            Don't use it. */
8565         cps->sendTime = 0;
8566     }
8567
8568     /*
8569      * If chess program startup fails, exit with an error message.
8570      * Attempts to recover here are futile. [HGM] Well, we try anyway
8571      */
8572     if ((StrStr(message, "unknown host") != NULL)
8573         || (StrStr(message, "No remote directory") != NULL)
8574         || (StrStr(message, "not found") != NULL)
8575         || (StrStr(message, "No such file") != NULL)
8576         || (StrStr(message, "can't alloc") != NULL)
8577         || (StrStr(message, "Permission denied") != NULL)) {
8578
8579         cps->maybeThinking = FALSE;
8580         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8581                 _(cps->which), cps->program, cps->host, message);
8582         RemoveInputSource(cps->isr);
8583         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8584             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8585             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8586         }
8587         return;
8588     }
8589
8590     /*
8591      * Look for hint output
8592      */
8593     if (sscanf(message, "Hint: %s", buf1) == 1) {
8594         if (cps == &first && hintRequested) {
8595             hintRequested = FALSE;
8596             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8597                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8598                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8599                                     PosFlags(forwardMostMove),
8600                                     fromY, fromX, toY, toX, promoChar, buf1);
8601                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8602                 DisplayInformation(buf2);
8603             } else {
8604                 /* Hint move could not be parsed!? */
8605               snprintf(buf2, sizeof(buf2),
8606                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8607                         buf1, _(cps->which));
8608                 DisplayError(buf2, 0);
8609             }
8610         } else {
8611           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8612         }
8613         return;
8614     }
8615
8616     /*
8617      * Ignore other messages if game is not in progress
8618      */
8619     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8620         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8621
8622     /*
8623      * look for win, lose, draw, or draw offer
8624      */
8625     if (strncmp(message, "1-0", 3) == 0) {
8626         char *p, *q, *r = "";
8627         p = strchr(message, '{');
8628         if (p) {
8629             q = strchr(p, '}');
8630             if (q) {
8631                 *q = NULLCHAR;
8632                 r = p + 1;
8633             }
8634         }
8635         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8636         return;
8637     } else if (strncmp(message, "0-1", 3) == 0) {
8638         char *p, *q, *r = "";
8639         p = strchr(message, '{');
8640         if (p) {
8641             q = strchr(p, '}');
8642             if (q) {
8643                 *q = NULLCHAR;
8644                 r = p + 1;
8645             }
8646         }
8647         /* Kludge for Arasan 4.1 bug */
8648         if (strcmp(r, "Black resigns") == 0) {
8649             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8650             return;
8651         }
8652         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8653         return;
8654     } else if (strncmp(message, "1/2", 3) == 0) {
8655         char *p, *q, *r = "";
8656         p = strchr(message, '{');
8657         if (p) {
8658             q = strchr(p, '}');
8659             if (q) {
8660                 *q = NULLCHAR;
8661                 r = p + 1;
8662             }
8663         }
8664
8665         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8666         return;
8667
8668     } else if (strncmp(message, "White resign", 12) == 0) {
8669         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8670         return;
8671     } else if (strncmp(message, "Black resign", 12) == 0) {
8672         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8673         return;
8674     } else if (strncmp(message, "White matches", 13) == 0 ||
8675                strncmp(message, "Black matches", 13) == 0   ) {
8676         /* [HGM] ignore GNUShogi noises */
8677         return;
8678     } else if (strncmp(message, "White", 5) == 0 &&
8679                message[5] != '(' &&
8680                StrStr(message, "Black") == NULL) {
8681         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8682         return;
8683     } else if (strncmp(message, "Black", 5) == 0 &&
8684                message[5] != '(') {
8685         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8686         return;
8687     } else if (strcmp(message, "resign") == 0 ||
8688                strcmp(message, "computer resigns") == 0) {
8689         switch (gameMode) {
8690           case MachinePlaysBlack:
8691           case IcsPlayingBlack:
8692             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8693             break;
8694           case MachinePlaysWhite:
8695           case IcsPlayingWhite:
8696             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8697             break;
8698           case TwoMachinesPlay:
8699             if (cps->twoMachinesColor[0] == 'w')
8700               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8701             else
8702               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8703             break;
8704           default:
8705             /* can't happen */
8706             break;
8707         }
8708         return;
8709     } else if (strncmp(message, "opponent mates", 14) == 0) {
8710         switch (gameMode) {
8711           case MachinePlaysBlack:
8712           case IcsPlayingBlack:
8713             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8714             break;
8715           case MachinePlaysWhite:
8716           case IcsPlayingWhite:
8717             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8718             break;
8719           case TwoMachinesPlay:
8720             if (cps->twoMachinesColor[0] == 'w')
8721               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8722             else
8723               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8724             break;
8725           default:
8726             /* can't happen */
8727             break;
8728         }
8729         return;
8730     } else if (strncmp(message, "computer mates", 14) == 0) {
8731         switch (gameMode) {
8732           case MachinePlaysBlack:
8733           case IcsPlayingBlack:
8734             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8735             break;
8736           case MachinePlaysWhite:
8737           case IcsPlayingWhite:
8738             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8739             break;
8740           case TwoMachinesPlay:
8741             if (cps->twoMachinesColor[0] == 'w')
8742               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8743             else
8744               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8745             break;
8746           default:
8747             /* can't happen */
8748             break;
8749         }
8750         return;
8751     } else if (strncmp(message, "checkmate", 9) == 0) {
8752         if (WhiteOnMove(forwardMostMove)) {
8753             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8754         } else {
8755             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8756         }
8757         return;
8758     } else if (strstr(message, "Draw") != NULL ||
8759                strstr(message, "game is a draw") != NULL) {
8760         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8761         return;
8762     } else if (strstr(message, "offer") != NULL &&
8763                strstr(message, "draw") != NULL) {
8764 #if ZIPPY
8765         if (appData.zippyPlay && first.initDone) {
8766             /* Relay offer to ICS */
8767             SendToICS(ics_prefix);
8768             SendToICS("draw\n");
8769         }
8770 #endif
8771         cps->offeredDraw = 2; /* valid until this engine moves twice */
8772         if (gameMode == TwoMachinesPlay) {
8773             if (cps->other->offeredDraw) {
8774                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8775             /* [HGM] in two-machine mode we delay relaying draw offer      */
8776             /* until after we also have move, to see if it is really claim */
8777             }
8778         } else if (gameMode == MachinePlaysWhite ||
8779                    gameMode == MachinePlaysBlack) {
8780           if (userOfferedDraw) {
8781             DisplayInformation(_("Machine accepts your draw offer"));
8782             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8783           } else {
8784             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8785           }
8786         }
8787     }
8788
8789
8790     /*
8791      * Look for thinking output
8792      */
8793     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8794           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8795                                 ) {
8796         int plylev, mvleft, mvtot, curscore, time;
8797         char mvname[MOVE_LEN];
8798         u64 nodes; // [DM]
8799         char plyext;
8800         int ignore = FALSE;
8801         int prefixHint = FALSE;
8802         mvname[0] = NULLCHAR;
8803
8804         switch (gameMode) {
8805           case MachinePlaysBlack:
8806           case IcsPlayingBlack:
8807             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8808             break;
8809           case MachinePlaysWhite:
8810           case IcsPlayingWhite:
8811             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8812             break;
8813           case AnalyzeMode:
8814           case AnalyzeFile:
8815             break;
8816           case IcsObserving: /* [DM] icsEngineAnalyze */
8817             if (!appData.icsEngineAnalyze) ignore = TRUE;
8818             break;
8819           case TwoMachinesPlay:
8820             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8821                 ignore = TRUE;
8822             }
8823             break;
8824           default:
8825             ignore = TRUE;
8826             break;
8827         }
8828
8829         if (!ignore) {
8830             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8831             buf1[0] = NULLCHAR;
8832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8834
8835                 if (plyext != ' ' && plyext != '\t') {
8836                     time *= 100;
8837                 }
8838
8839                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8840                 if( cps->scoreIsAbsolute &&
8841                     ( gameMode == MachinePlaysBlack ||
8842                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8843                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8844                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8845                      !WhiteOnMove(currentMove)
8846                     ) )
8847                 {
8848                     curscore = -curscore;
8849                 }
8850
8851                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8852
8853                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8854                         char buf[MSG_SIZ];
8855                         FILE *f;
8856                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8857                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8858                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8859                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8860                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8861                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8862                                 fclose(f);
8863                         } else DisplayError(_("failed writing PV"), 0);
8864                 }
8865
8866                 tempStats.depth = plylev;
8867                 tempStats.nodes = nodes;
8868                 tempStats.time = time;
8869                 tempStats.score = curscore;
8870                 tempStats.got_only_move = 0;
8871
8872                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8873                         int ticklen;
8874
8875                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8876                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8877                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8878                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8879                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8880                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8881                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8882                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8883                 }
8884
8885                 /* Buffer overflow protection */
8886                 if (pv[0] != NULLCHAR) {
8887                     if (strlen(pv) >= sizeof(tempStats.movelist)
8888                         && appData.debugMode) {
8889                         fprintf(debugFP,
8890                                 "PV is too long; using the first %u bytes.\n",
8891                                 (unsigned) sizeof(tempStats.movelist) - 1);
8892                     }
8893
8894                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8895                 } else {
8896                     sprintf(tempStats.movelist, " no PV\n");
8897                 }
8898
8899                 if (tempStats.seen_stat) {
8900                     tempStats.ok_to_send = 1;
8901                 }
8902
8903                 if (strchr(tempStats.movelist, '(') != NULL) {
8904                     tempStats.line_is_book = 1;
8905                     tempStats.nr_moves = 0;
8906                     tempStats.moves_left = 0;
8907                 } else {
8908                     tempStats.line_is_book = 0;
8909                 }
8910
8911                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8912                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8913
8914                 SendProgramStatsToFrontend( cps, &tempStats );
8915
8916                 /*
8917                     [AS] Protect the thinkOutput buffer from overflow... this
8918                     is only useful if buf1 hasn't overflowed first!
8919                 */
8920                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8921                          plylev,
8922                          (gameMode == TwoMachinesPlay ?
8923                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8924                          ((double) curscore) / 100.0,
8925                          prefixHint ? lastHint : "",
8926                          prefixHint ? " " : "" );
8927
8928                 if( buf1[0] != NULLCHAR ) {
8929                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8930
8931                     if( strlen(pv) > max_len ) {
8932                         if( appData.debugMode) {
8933                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8934                         }
8935                         pv[max_len+1] = '\0';
8936                     }
8937
8938                     strcat( thinkOutput, pv);
8939                 }
8940
8941                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8942                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8943                     DisplayMove(currentMove - 1);
8944                 }
8945                 return;
8946
8947             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8948                 /* crafty (9.25+) says "(only move) <move>"
8949                  * if there is only 1 legal move
8950                  */
8951                 sscanf(p, "(only move) %s", buf1);
8952                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8953                 sprintf(programStats.movelist, "%s (only move)", buf1);
8954                 programStats.depth = 1;
8955                 programStats.nr_moves = 1;
8956                 programStats.moves_left = 1;
8957                 programStats.nodes = 1;
8958                 programStats.time = 1;
8959                 programStats.got_only_move = 1;
8960
8961                 /* Not really, but we also use this member to
8962                    mean "line isn't going to change" (Crafty
8963                    isn't searching, so stats won't change) */
8964                 programStats.line_is_book = 1;
8965
8966                 SendProgramStatsToFrontend( cps, &programStats );
8967
8968                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8969                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8970                     DisplayMove(currentMove - 1);
8971                 }
8972                 return;
8973             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8974                               &time, &nodes, &plylev, &mvleft,
8975                               &mvtot, mvname) >= 5) {
8976                 /* The stat01: line is from Crafty (9.29+) in response
8977                    to the "." command */
8978                 programStats.seen_stat = 1;
8979                 cps->maybeThinking = TRUE;
8980
8981                 if (programStats.got_only_move || !appData.periodicUpdates)
8982                   return;
8983
8984                 programStats.depth = plylev;
8985                 programStats.time = time;
8986                 programStats.nodes = nodes;
8987                 programStats.moves_left = mvleft;
8988                 programStats.nr_moves = mvtot;
8989                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8990                 programStats.ok_to_send = 1;
8991                 programStats.movelist[0] = '\0';
8992
8993                 SendProgramStatsToFrontend( cps, &programStats );
8994
8995                 return;
8996
8997             } else if (strncmp(message,"++",2) == 0) {
8998                 /* Crafty 9.29+ outputs this */
8999                 programStats.got_fail = 2;
9000                 return;
9001
9002             } else if (strncmp(message,"--",2) == 0) {
9003                 /* Crafty 9.29+ outputs this */
9004                 programStats.got_fail = 1;
9005                 return;
9006
9007             } else if (thinkOutput[0] != NULLCHAR &&
9008                        strncmp(message, "    ", 4) == 0) {
9009                 unsigned message_len;
9010
9011                 p = message;
9012                 while (*p && *p == ' ') p++;
9013
9014                 message_len = strlen( p );
9015
9016                 /* [AS] Avoid buffer overflow */
9017                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9018                     strcat(thinkOutput, " ");
9019                     strcat(thinkOutput, p);
9020                 }
9021
9022                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9023                     strcat(programStats.movelist, " ");
9024                     strcat(programStats.movelist, p);
9025                 }
9026
9027                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9028                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9029                     DisplayMove(currentMove - 1);
9030                 }
9031                 return;
9032             }
9033         }
9034         else {
9035             buf1[0] = NULLCHAR;
9036
9037             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9038                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9039             {
9040                 ChessProgramStats cpstats;
9041
9042                 if (plyext != ' ' && plyext != '\t') {
9043                     time *= 100;
9044                 }
9045
9046                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9047                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9048                     curscore = -curscore;
9049                 }
9050
9051                 cpstats.depth = plylev;
9052                 cpstats.nodes = nodes;
9053                 cpstats.time = time;
9054                 cpstats.score = curscore;
9055                 cpstats.got_only_move = 0;
9056                 cpstats.movelist[0] = '\0';
9057
9058                 if (buf1[0] != NULLCHAR) {
9059                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9060                 }
9061
9062                 cpstats.ok_to_send = 0;
9063                 cpstats.line_is_book = 0;
9064                 cpstats.nr_moves = 0;
9065                 cpstats.moves_left = 0;
9066
9067                 SendProgramStatsToFrontend( cps, &cpstats );
9068             }
9069         }
9070     }
9071 }
9072
9073
9074 /* Parse a game score from the character string "game", and
9075    record it as the history of the current game.  The game
9076    score is NOT assumed to start from the standard position.
9077    The display is not updated in any way.
9078    */
9079 void
9080 ParseGameHistory (char *game)
9081 {
9082     ChessMove moveType;
9083     int fromX, fromY, toX, toY, boardIndex;
9084     char promoChar;
9085     char *p, *q;
9086     char buf[MSG_SIZ];
9087
9088     if (appData.debugMode)
9089       fprintf(debugFP, "Parsing game history: %s\n", game);
9090
9091     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9092     gameInfo.site = StrSave(appData.icsHost);
9093     gameInfo.date = PGNDate();
9094     gameInfo.round = StrSave("-");
9095
9096     /* Parse out names of players */
9097     while (*game == ' ') game++;
9098     p = buf;
9099     while (*game != ' ') *p++ = *game++;
9100     *p = NULLCHAR;
9101     gameInfo.white = StrSave(buf);
9102     while (*game == ' ') game++;
9103     p = buf;
9104     while (*game != ' ' && *game != '\n') *p++ = *game++;
9105     *p = NULLCHAR;
9106     gameInfo.black = StrSave(buf);
9107
9108     /* Parse moves */
9109     boardIndex = blackPlaysFirst ? 1 : 0;
9110     yynewstr(game);
9111     for (;;) {
9112         yyboardindex = boardIndex;
9113         moveType = (ChessMove) Myylex();
9114         switch (moveType) {
9115           case IllegalMove:             /* maybe suicide chess, etc. */
9116   if (appData.debugMode) {
9117     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9118     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9119     setbuf(debugFP, NULL);
9120   }
9121           case WhitePromotion:
9122           case BlackPromotion:
9123           case WhiteNonPromotion:
9124           case BlackNonPromotion:
9125           case NormalMove:
9126           case WhiteCapturesEnPassant:
9127           case BlackCapturesEnPassant:
9128           case WhiteKingSideCastle:
9129           case WhiteQueenSideCastle:
9130           case BlackKingSideCastle:
9131           case BlackQueenSideCastle:
9132           case WhiteKingSideCastleWild:
9133           case WhiteQueenSideCastleWild:
9134           case BlackKingSideCastleWild:
9135           case BlackQueenSideCastleWild:
9136           /* PUSH Fabien */
9137           case WhiteHSideCastleFR:
9138           case WhiteASideCastleFR:
9139           case BlackHSideCastleFR:
9140           case BlackASideCastleFR:
9141           /* POP Fabien */
9142             fromX = currentMoveString[0] - AAA;
9143             fromY = currentMoveString[1] - ONE;
9144             toX = currentMoveString[2] - AAA;
9145             toY = currentMoveString[3] - ONE;
9146             promoChar = currentMoveString[4];
9147             break;
9148           case WhiteDrop:
9149           case BlackDrop:
9150             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9151             fromX = moveType == WhiteDrop ?
9152               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9153             (int) CharToPiece(ToLower(currentMoveString[0]));
9154             fromY = DROP_RANK;
9155             toX = currentMoveString[2] - AAA;
9156             toY = currentMoveString[3] - ONE;
9157             promoChar = NULLCHAR;
9158             break;
9159           case AmbiguousMove:
9160             /* bug? */
9161             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9162   if (appData.debugMode) {
9163     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9164     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9165     setbuf(debugFP, NULL);
9166   }
9167             DisplayError(buf, 0);
9168             return;
9169           case ImpossibleMove:
9170             /* bug? */
9171             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9172   if (appData.debugMode) {
9173     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9174     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9175     setbuf(debugFP, NULL);
9176   }
9177             DisplayError(buf, 0);
9178             return;
9179           case EndOfFile:
9180             if (boardIndex < backwardMostMove) {
9181                 /* Oops, gap.  How did that happen? */
9182                 DisplayError(_("Gap in move list"), 0);
9183                 return;
9184             }
9185             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9186             if (boardIndex > forwardMostMove) {
9187                 forwardMostMove = boardIndex;
9188             }
9189             return;
9190           case ElapsedTime:
9191             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9192                 strcat(parseList[boardIndex-1], " ");
9193                 strcat(parseList[boardIndex-1], yy_text);
9194             }
9195             continue;
9196           case Comment:
9197           case PGNTag:
9198           case NAG:
9199           default:
9200             /* ignore */
9201             continue;
9202           case WhiteWins:
9203           case BlackWins:
9204           case GameIsDrawn:
9205           case GameUnfinished:
9206             if (gameMode == IcsExamining) {
9207                 if (boardIndex < backwardMostMove) {
9208                     /* Oops, gap.  How did that happen? */
9209                     return;
9210                 }
9211                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9212                 return;
9213             }
9214             gameInfo.result = moveType;
9215             p = strchr(yy_text, '{');
9216             if (p == NULL) p = strchr(yy_text, '(');
9217             if (p == NULL) {
9218                 p = yy_text;
9219                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9220             } else {
9221                 q = strchr(p, *p == '{' ? '}' : ')');
9222                 if (q != NULL) *q = NULLCHAR;
9223                 p++;
9224             }
9225             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9226             gameInfo.resultDetails = StrSave(p);
9227             continue;
9228         }
9229         if (boardIndex >= forwardMostMove &&
9230             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9231             backwardMostMove = blackPlaysFirst ? 1 : 0;
9232             return;
9233         }
9234         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9235                                  fromY, fromX, toY, toX, promoChar,
9236                                  parseList[boardIndex]);
9237         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9238         /* currentMoveString is set as a side-effect of yylex */
9239         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9240         strcat(moveList[boardIndex], "\n");
9241         boardIndex++;
9242         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9243         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9244           case MT_NONE:
9245           case MT_STALEMATE:
9246           default:
9247             break;
9248           case MT_CHECK:
9249             if(gameInfo.variant != VariantShogi)
9250                 strcat(parseList[boardIndex - 1], "+");
9251             break;
9252           case MT_CHECKMATE:
9253           case MT_STAINMATE:
9254             strcat(parseList[boardIndex - 1], "#");
9255             break;
9256         }
9257     }
9258 }
9259
9260
9261 /* Apply a move to the given board  */
9262 void
9263 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9264 {
9265   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9266   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9267
9268     /* [HGM] compute & store e.p. status and castling rights for new position */
9269     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9270
9271       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9272       oldEP = (signed char)board[EP_STATUS];
9273       board[EP_STATUS] = EP_NONE;
9274
9275   if (fromY == DROP_RANK) {
9276         /* must be first */
9277         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9278             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9279             return;
9280         }
9281         piece = board[toY][toX] = (ChessSquare) fromX;
9282   } else {
9283       int i;
9284
9285       if( board[toY][toX] != EmptySquare )
9286            board[EP_STATUS] = EP_CAPTURE;
9287
9288       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9289            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9290                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9291       } else
9292       if( board[fromY][fromX] == WhitePawn ) {
9293            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9294                board[EP_STATUS] = EP_PAWN_MOVE;
9295            if( toY-fromY==2) {
9296                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9297                         gameInfo.variant != VariantBerolina || toX < fromX)
9298                       board[EP_STATUS] = toX | berolina;
9299                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9300                         gameInfo.variant != VariantBerolina || toX > fromX)
9301                       board[EP_STATUS] = toX;
9302            }
9303       } else
9304       if( board[fromY][fromX] == BlackPawn ) {
9305            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9306                board[EP_STATUS] = EP_PAWN_MOVE;
9307            if( toY-fromY== -2) {
9308                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9309                         gameInfo.variant != VariantBerolina || toX < fromX)
9310                       board[EP_STATUS] = toX | berolina;
9311                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9312                         gameInfo.variant != VariantBerolina || toX > fromX)
9313                       board[EP_STATUS] = toX;
9314            }
9315        }
9316
9317        for(i=0; i<nrCastlingRights; i++) {
9318            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9319               board[CASTLING][i] == toX   && castlingRank[i] == toY
9320              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9321        }
9322
9323        if(gameInfo.variant == VariantSChess) { // update virginity
9324            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9325            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9326            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9327            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9328        }
9329
9330      if (fromX == toX && fromY == toY) return;
9331
9332      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9333      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9334      if(gameInfo.variant == VariantKnightmate)
9335          king += (int) WhiteUnicorn - (int) WhiteKing;
9336
9337     /* Code added by Tord: */
9338     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9339     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9340         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9341       board[fromY][fromX] = EmptySquare;
9342       board[toY][toX] = EmptySquare;
9343       if((toX > fromX) != (piece == WhiteRook)) {
9344         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9345       } else {
9346         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9347       }
9348     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9349                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9350       board[fromY][fromX] = EmptySquare;
9351       board[toY][toX] = EmptySquare;
9352       if((toX > fromX) != (piece == BlackRook)) {
9353         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9354       } else {
9355         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9356       }
9357     /* End of code added by Tord */
9358
9359     } else if (board[fromY][fromX] == king
9360         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9361         && toY == fromY && toX > fromX+1) {
9362         board[fromY][fromX] = EmptySquare;
9363         board[toY][toX] = king;
9364         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9365         board[fromY][BOARD_RGHT-1] = EmptySquare;
9366     } else if (board[fromY][fromX] == king
9367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9368                && toY == fromY && toX < fromX-1) {
9369         board[fromY][fromX] = EmptySquare;
9370         board[toY][toX] = king;
9371         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9372         board[fromY][BOARD_LEFT] = EmptySquare;
9373     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9374                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9375                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9376                ) {
9377         /* white pawn promotion */
9378         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9379         if(gameInfo.variant==VariantBughouse ||
9380            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9381             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9382         board[fromY][fromX] = EmptySquare;
9383     } else if ((fromY >= BOARD_HEIGHT>>1)
9384                && (toX != fromX)
9385                && gameInfo.variant != VariantXiangqi
9386                && gameInfo.variant != VariantBerolina
9387                && (board[fromY][fromX] == WhitePawn)
9388                && (board[toY][toX] == EmptySquare)) {
9389         board[fromY][fromX] = EmptySquare;
9390         board[toY][toX] = WhitePawn;
9391         captured = board[toY - 1][toX];
9392         board[toY - 1][toX] = EmptySquare;
9393     } else if ((fromY == BOARD_HEIGHT-4)
9394                && (toX == fromX)
9395                && gameInfo.variant == VariantBerolina
9396                && (board[fromY][fromX] == WhitePawn)
9397                && (board[toY][toX] == EmptySquare)) {
9398         board[fromY][fromX] = EmptySquare;
9399         board[toY][toX] = WhitePawn;
9400         if(oldEP & EP_BEROLIN_A) {
9401                 captured = board[fromY][fromX-1];
9402                 board[fromY][fromX-1] = EmptySquare;
9403         }else{  captured = board[fromY][fromX+1];
9404                 board[fromY][fromX+1] = EmptySquare;
9405         }
9406     } else if (board[fromY][fromX] == king
9407         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9408                && toY == fromY && toX > fromX+1) {
9409         board[fromY][fromX] = EmptySquare;
9410         board[toY][toX] = king;
9411         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9412         board[fromY][BOARD_RGHT-1] = EmptySquare;
9413     } else if (board[fromY][fromX] == king
9414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9415                && toY == fromY && toX < fromX-1) {
9416         board[fromY][fromX] = EmptySquare;
9417         board[toY][toX] = king;
9418         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9419         board[fromY][BOARD_LEFT] = EmptySquare;
9420     } else if (fromY == 7 && fromX == 3
9421                && board[fromY][fromX] == BlackKing
9422                && toY == 7 && toX == 5) {
9423         board[fromY][fromX] = EmptySquare;
9424         board[toY][toX] = BlackKing;
9425         board[fromY][7] = EmptySquare;
9426         board[toY][4] = BlackRook;
9427     } else if (fromY == 7 && fromX == 3
9428                && board[fromY][fromX] == BlackKing
9429                && toY == 7 && toX == 1) {
9430         board[fromY][fromX] = EmptySquare;
9431         board[toY][toX] = BlackKing;
9432         board[fromY][0] = EmptySquare;
9433         board[toY][2] = BlackRook;
9434     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9435                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9436                && toY < promoRank && promoChar
9437                ) {
9438         /* black pawn promotion */
9439         board[toY][toX] = CharToPiece(ToLower(promoChar));
9440         if(gameInfo.variant==VariantBughouse ||
9441            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9442             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9443         board[fromY][fromX] = EmptySquare;
9444     } else if ((fromY < BOARD_HEIGHT>>1)
9445                && (toX != fromX)
9446                && gameInfo.variant != VariantXiangqi
9447                && gameInfo.variant != VariantBerolina
9448                && (board[fromY][fromX] == BlackPawn)
9449                && (board[toY][toX] == EmptySquare)) {
9450         board[fromY][fromX] = EmptySquare;
9451         board[toY][toX] = BlackPawn;
9452         captured = board[toY + 1][toX];
9453         board[toY + 1][toX] = EmptySquare;
9454     } else if ((fromY == 3)
9455                && (toX == fromX)
9456                && gameInfo.variant == VariantBerolina
9457                && (board[fromY][fromX] == BlackPawn)
9458                && (board[toY][toX] == EmptySquare)) {
9459         board[fromY][fromX] = EmptySquare;
9460         board[toY][toX] = BlackPawn;
9461         if(oldEP & EP_BEROLIN_A) {
9462                 captured = board[fromY][fromX-1];
9463                 board[fromY][fromX-1] = EmptySquare;
9464         }else{  captured = board[fromY][fromX+1];
9465                 board[fromY][fromX+1] = EmptySquare;
9466         }
9467     } else {
9468         board[toY][toX] = board[fromY][fromX];
9469         board[fromY][fromX] = EmptySquare;
9470     }
9471   }
9472
9473     if (gameInfo.holdingsWidth != 0) {
9474
9475       /* !!A lot more code needs to be written to support holdings  */
9476       /* [HGM] OK, so I have written it. Holdings are stored in the */
9477       /* penultimate board files, so they are automaticlly stored   */
9478       /* in the game history.                                       */
9479       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9480                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9481         /* Delete from holdings, by decreasing count */
9482         /* and erasing image if necessary            */
9483         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9484         if(p < (int) BlackPawn) { /* white drop */
9485              p -= (int)WhitePawn;
9486                  p = PieceToNumber((ChessSquare)p);
9487              if(p >= gameInfo.holdingsSize) p = 0;
9488              if(--board[p][BOARD_WIDTH-2] <= 0)
9489                   board[p][BOARD_WIDTH-1] = EmptySquare;
9490              if((int)board[p][BOARD_WIDTH-2] < 0)
9491                         board[p][BOARD_WIDTH-2] = 0;
9492         } else {                  /* black drop */
9493              p -= (int)BlackPawn;
9494                  p = PieceToNumber((ChessSquare)p);
9495              if(p >= gameInfo.holdingsSize) p = 0;
9496              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9497                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9498              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9499                         board[BOARD_HEIGHT-1-p][1] = 0;
9500         }
9501       }
9502       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9503           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9504         /* [HGM] holdings: Add to holdings, if holdings exist */
9505         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9506                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9507                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9508         }
9509         p = (int) captured;
9510         if (p >= (int) BlackPawn) {
9511           p -= (int)BlackPawn;
9512           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9513                   /* in Shogi restore piece to its original  first */
9514                   captured = (ChessSquare) (DEMOTED captured);
9515                   p = DEMOTED p;
9516           }
9517           p = PieceToNumber((ChessSquare)p);
9518           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9519           board[p][BOARD_WIDTH-2]++;
9520           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9521         } else {
9522           p -= (int)WhitePawn;
9523           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9524                   captured = (ChessSquare) (DEMOTED captured);
9525                   p = DEMOTED p;
9526           }
9527           p = PieceToNumber((ChessSquare)p);
9528           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9529           board[BOARD_HEIGHT-1-p][1]++;
9530           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9531         }
9532       }
9533     } else if (gameInfo.variant == VariantAtomic) {
9534       if (captured != EmptySquare) {
9535         int y, x;
9536         for (y = toY-1; y <= toY+1; y++) {
9537           for (x = toX-1; x <= toX+1; x++) {
9538             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9539                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9540               board[y][x] = EmptySquare;
9541             }
9542           }
9543         }
9544         board[toY][toX] = EmptySquare;
9545       }
9546     }
9547     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9548         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9549     } else
9550     if(promoChar == '+') {
9551         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9552         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9553     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9554         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9555         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9556            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9557         board[toY][toX] = newPiece;
9558     }
9559     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9560                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9561         // [HGM] superchess: take promotion piece out of holdings
9562         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9563         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9564             if(!--board[k][BOARD_WIDTH-2])
9565                 board[k][BOARD_WIDTH-1] = EmptySquare;
9566         } else {
9567             if(!--board[BOARD_HEIGHT-1-k][1])
9568                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9569         }
9570     }
9571
9572 }
9573
9574 /* Updates forwardMostMove */
9575 void
9576 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9577 {
9578 //    forwardMostMove++; // [HGM] bare: moved downstream
9579
9580     (void) CoordsToAlgebraic(boards[forwardMostMove],
9581                              PosFlags(forwardMostMove),
9582                              fromY, fromX, toY, toX, promoChar,
9583                              parseList[forwardMostMove]);
9584
9585     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9586         int timeLeft; static int lastLoadFlag=0; int king, piece;
9587         piece = boards[forwardMostMove][fromY][fromX];
9588         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9589         if(gameInfo.variant == VariantKnightmate)
9590             king += (int) WhiteUnicorn - (int) WhiteKing;
9591         if(forwardMostMove == 0) {
9592             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9593                 fprintf(serverMoves, "%s;", UserName());
9594             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9595                 fprintf(serverMoves, "%s;", second.tidy);
9596             fprintf(serverMoves, "%s;", first.tidy);
9597             if(gameMode == MachinePlaysWhite)
9598                 fprintf(serverMoves, "%s;", UserName());
9599             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9600                 fprintf(serverMoves, "%s;", second.tidy);
9601         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9602         lastLoadFlag = loadFlag;
9603         // print base move
9604         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9605         // print castling suffix
9606         if( toY == fromY && piece == king ) {
9607             if(toX-fromX > 1)
9608                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9609             if(fromX-toX >1)
9610                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9611         }
9612         // e.p. suffix
9613         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9614              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9615              boards[forwardMostMove][toY][toX] == EmptySquare
9616              && fromX != toX && fromY != toY)
9617                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9618         // promotion suffix
9619         if(promoChar != NULLCHAR)
9620                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9621         if(!loadFlag) {
9622                 char buf[MOVE_LEN*2], *p; int len;
9623             fprintf(serverMoves, "/%d/%d",
9624                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9625             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9626             else                      timeLeft = blackTimeRemaining/1000;
9627             fprintf(serverMoves, "/%d", timeLeft);
9628                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9629                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9630                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9631             fprintf(serverMoves, "/%s", buf);
9632         }
9633         fflush(serverMoves);
9634     }
9635
9636     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9637         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9638       return;
9639     }
9640     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9641     if (commentList[forwardMostMove+1] != NULL) {
9642         free(commentList[forwardMostMove+1]);
9643         commentList[forwardMostMove+1] = NULL;
9644     }
9645     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9646     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9647     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9648     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9649     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9650     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9651     adjustedClock = FALSE;
9652     gameInfo.result = GameUnfinished;
9653     if (gameInfo.resultDetails != NULL) {
9654         free(gameInfo.resultDetails);
9655         gameInfo.resultDetails = NULL;
9656     }
9657     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9658                               moveList[forwardMostMove - 1]);
9659     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9660       case MT_NONE:
9661       case MT_STALEMATE:
9662       default:
9663         break;
9664       case MT_CHECK:
9665         if(gameInfo.variant != VariantShogi)
9666             strcat(parseList[forwardMostMove - 1], "+");
9667         break;
9668       case MT_CHECKMATE:
9669       case MT_STAINMATE:
9670         strcat(parseList[forwardMostMove - 1], "#");
9671         break;
9672     }
9673
9674 }
9675
9676 /* Updates currentMove if not pausing */
9677 void
9678 ShowMove (int fromX, int fromY, int toX, int toY)
9679 {
9680     int instant = (gameMode == PlayFromGameFile) ?
9681         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9682     if(appData.noGUI) return;
9683     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9684         if (!instant) {
9685             if (forwardMostMove == currentMove + 1) {
9686                 AnimateMove(boards[forwardMostMove - 1],
9687                             fromX, fromY, toX, toY);
9688             }
9689             if (appData.highlightLastMove) {
9690                 SetHighlights(fromX, fromY, toX, toY);
9691             }
9692         }
9693         currentMove = forwardMostMove;
9694     }
9695
9696     if (instant) return;
9697
9698     DisplayMove(currentMove - 1);
9699     DrawPosition(FALSE, boards[currentMove]);
9700     DisplayBothClocks();
9701     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9702 }
9703
9704 void
9705 SendEgtPath (ChessProgramState *cps)
9706 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9707         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9708
9709         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9710
9711         while(*p) {
9712             char c, *q = name+1, *r, *s;
9713
9714             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9715             while(*p && *p != ',') *q++ = *p++;
9716             *q++ = ':'; *q = 0;
9717             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9718                 strcmp(name, ",nalimov:") == 0 ) {
9719                 // take nalimov path from the menu-changeable option first, if it is defined
9720               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9721                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9722             } else
9723             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9724                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9725                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9726                 s = r = StrStr(s, ":") + 1; // beginning of path info
9727                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9728                 c = *r; *r = 0;             // temporarily null-terminate path info
9729                     *--q = 0;               // strip of trailig ':' from name
9730                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9731                 *r = c;
9732                 SendToProgram(buf,cps);     // send egtbpath command for this format
9733             }
9734             if(*p == ',') p++; // read away comma to position for next format name
9735         }
9736 }
9737
9738 void
9739 InitChessProgram (ChessProgramState *cps, int setup)
9740 /* setup needed to setup FRC opening position */
9741 {
9742     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9743     if (appData.noChessProgram) return;
9744     hintRequested = FALSE;
9745     bookRequested = FALSE;
9746
9747     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9748     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9749     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9750     if(cps->memSize) { /* [HGM] memory */
9751       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9752         SendToProgram(buf, cps);
9753     }
9754     SendEgtPath(cps); /* [HGM] EGT */
9755     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9756       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9757         SendToProgram(buf, cps);
9758     }
9759
9760     SendToProgram(cps->initString, cps);
9761     if (gameInfo.variant != VariantNormal &&
9762         gameInfo.variant != VariantLoadable
9763         /* [HGM] also send variant if board size non-standard */
9764         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9765                                             ) {
9766       char *v = VariantName(gameInfo.variant);
9767       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9768         /* [HGM] in protocol 1 we have to assume all variants valid */
9769         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9770         DisplayFatalError(buf, 0, 1);
9771         return;
9772       }
9773
9774       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9775       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9776       if( gameInfo.variant == VariantXiangqi )
9777            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9778       if( gameInfo.variant == VariantShogi )
9779            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9780       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9781            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9782       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9783           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9784            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9785       if( gameInfo.variant == VariantCourier )
9786            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9787       if( gameInfo.variant == VariantSuper )
9788            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9789       if( gameInfo.variant == VariantGreat )
9790            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9791       if( gameInfo.variant == VariantSChess )
9792            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9793       if( gameInfo.variant == VariantGrand )
9794            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9795
9796       if(overruled) {
9797         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9798                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9799            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9800            if(StrStr(cps->variants, b) == NULL) {
9801                // specific sized variant not known, check if general sizing allowed
9802                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9803                    if(StrStr(cps->variants, "boardsize") == NULL) {
9804                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9805                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9806                        DisplayFatalError(buf, 0, 1);
9807                        return;
9808                    }
9809                    /* [HGM] here we really should compare with the maximum supported board size */
9810                }
9811            }
9812       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9813       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9814       SendToProgram(buf, cps);
9815     }
9816     currentlyInitializedVariant = gameInfo.variant;
9817
9818     /* [HGM] send opening position in FRC to first engine */
9819     if(setup) {
9820           SendToProgram("force\n", cps);
9821           SendBoard(cps, 0);
9822           /* engine is now in force mode! Set flag to wake it up after first move. */
9823           setboardSpoiledMachineBlack = 1;
9824     }
9825
9826     if (cps->sendICS) {
9827       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9828       SendToProgram(buf, cps);
9829     }
9830     cps->maybeThinking = FALSE;
9831     cps->offeredDraw = 0;
9832     if (!appData.icsActive) {
9833         SendTimeControl(cps, movesPerSession, timeControl,
9834                         timeIncrement, appData.searchDepth,
9835                         searchTime);
9836     }
9837     if (appData.showThinking
9838         // [HGM] thinking: four options require thinking output to be sent
9839         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9840                                 ) {
9841         SendToProgram("post\n", cps);
9842     }
9843     SendToProgram("hard\n", cps);
9844     if (!appData.ponderNextMove) {
9845         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9846            it without being sure what state we are in first.  "hard"
9847            is not a toggle, so that one is OK.
9848          */
9849         SendToProgram("easy\n", cps);
9850     }
9851     if (cps->usePing) {
9852       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9853       SendToProgram(buf, cps);
9854     }
9855     cps->initDone = TRUE;
9856     ClearEngineOutputPane(cps == &second);
9857 }
9858
9859
9860 void
9861 StartChessProgram (ChessProgramState *cps)
9862 {
9863     char buf[MSG_SIZ];
9864     int err;
9865
9866     if (appData.noChessProgram) return;
9867     cps->initDone = FALSE;
9868
9869     if (strcmp(cps->host, "localhost") == 0) {
9870         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9871     } else if (*appData.remoteShell == NULLCHAR) {
9872         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9873     } else {
9874         if (*appData.remoteUser == NULLCHAR) {
9875           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9876                     cps->program);
9877         } else {
9878           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9879                     cps->host, appData.remoteUser, cps->program);
9880         }
9881         err = StartChildProcess(buf, "", &cps->pr);
9882     }
9883
9884     if (err != 0) {
9885       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9886         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9887         if(cps != &first) return;
9888         appData.noChessProgram = TRUE;
9889         ThawUI();
9890         SetNCPMode();
9891 //      DisplayFatalError(buf, err, 1);
9892 //      cps->pr = NoProc;
9893 //      cps->isr = NULL;
9894         return;
9895     }
9896
9897     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9898     if (cps->protocolVersion > 1) {
9899       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9900       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9901       cps->comboCnt = 0;  //                and values of combo boxes
9902       SendToProgram(buf, cps);
9903     } else {
9904       SendToProgram("xboard\n", cps);
9905     }
9906 }
9907
9908 void
9909 TwoMachinesEventIfReady P((void))
9910 {
9911   static int curMess = 0;
9912   if (first.lastPing != first.lastPong) {
9913     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9914     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9915     return;
9916   }
9917   if (second.lastPing != second.lastPong) {
9918     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9919     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9920     return;
9921   }
9922   DisplayMessage("", ""); curMess = 0;
9923   ThawUI();
9924   TwoMachinesEvent();
9925 }
9926
9927 char *
9928 MakeName (char *template)
9929 {
9930     time_t clock;
9931     struct tm *tm;
9932     static char buf[MSG_SIZ];
9933     char *p = buf;
9934     int i;
9935
9936     clock = time((time_t *)NULL);
9937     tm = localtime(&clock);
9938
9939     while(*p++ = *template++) if(p[-1] == '%') {
9940         switch(*template++) {
9941           case 0:   *p = 0; return buf;
9942           case 'Y': i = tm->tm_year+1900; break;
9943           case 'y': i = tm->tm_year-100; break;
9944           case 'M': i = tm->tm_mon+1; break;
9945           case 'd': i = tm->tm_mday; break;
9946           case 'h': i = tm->tm_hour; break;
9947           case 'm': i = tm->tm_min; break;
9948           case 's': i = tm->tm_sec; break;
9949           default:  i = 0;
9950         }
9951         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9952     }
9953     return buf;
9954 }
9955
9956 int
9957 CountPlayers (char *p)
9958 {
9959     int n = 0;
9960     while(p = strchr(p, '\n')) p++, n++; // count participants
9961     return n;
9962 }
9963
9964 FILE *
9965 WriteTourneyFile (char *results, FILE *f)
9966 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9967     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9968     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9969         // create a file with tournament description
9970         fprintf(f, "-participants {%s}\n", appData.participants);
9971         fprintf(f, "-seedBase %d\n", appData.seedBase);
9972         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9973         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9974         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9975         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9976         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9977         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9978         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9979         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9980         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9981         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9982         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9983         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9984         if(searchTime > 0)
9985                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9986         else {
9987                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9988                 fprintf(f, "-tc %s\n", appData.timeControl);
9989                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9990         }
9991         fprintf(f, "-results \"%s\"\n", results);
9992     }
9993     return f;
9994 }
9995
9996 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9997
9998 void
9999 Substitute (char *participants, int expunge)
10000 {
10001     int i, changed, changes=0, nPlayers=0;
10002     char *p, *q, *r, buf[MSG_SIZ];
10003     if(participants == NULL) return;
10004     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10005     r = p = participants; q = appData.participants;
10006     while(*p && *p == *q) {
10007         if(*p == '\n') r = p+1, nPlayers++;
10008         p++; q++;
10009     }
10010     if(*p) { // difference
10011         while(*p && *p++ != '\n');
10012         while(*q && *q++ != '\n');
10013       changed = nPlayers;
10014         changes = 1 + (strcmp(p, q) != 0);
10015     }
10016     if(changes == 1) { // a single engine mnemonic was changed
10017         q = r; while(*q) nPlayers += (*q++ == '\n');
10018         p = buf; while(*r && (*p = *r++) != '\n') p++;
10019         *p = NULLCHAR;
10020         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10021         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10022         if(mnemonic[i]) { // The substitute is valid
10023             FILE *f;
10024             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10025                 flock(fileno(f), LOCK_EX);
10026                 ParseArgsFromFile(f);
10027                 fseek(f, 0, SEEK_SET);
10028                 FREE(appData.participants); appData.participants = participants;
10029                 if(expunge) { // erase results of replaced engine
10030                     int len = strlen(appData.results), w, b, dummy;
10031                     for(i=0; i<len; i++) {
10032                         Pairing(i, nPlayers, &w, &b, &dummy);
10033                         if((w == changed || b == changed) && appData.results[i] == '*') {
10034                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10035                             fclose(f);
10036                             return;
10037                         }
10038                     }
10039                     for(i=0; i<len; i++) {
10040                         Pairing(i, nPlayers, &w, &b, &dummy);
10041                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10042                     }
10043                 }
10044                 WriteTourneyFile(appData.results, f);
10045                 fclose(f); // release lock
10046                 return;
10047             }
10048         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10049     }
10050     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10051     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10052     free(participants);
10053     return;
10054 }
10055
10056 int
10057 CreateTourney (char *name)
10058 {
10059         FILE *f;
10060         if(matchMode && strcmp(name, appData.tourneyFile)) {
10061              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10062         }
10063         if(name[0] == NULLCHAR) {
10064             if(appData.participants[0])
10065                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10066             return 0;
10067         }
10068         f = fopen(name, "r");
10069         if(f) { // file exists
10070             ASSIGN(appData.tourneyFile, name);
10071             ParseArgsFromFile(f); // parse it
10072         } else {
10073             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10074             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10075                 DisplayError(_("Not enough participants"), 0);
10076                 return 0;
10077             }
10078             ASSIGN(appData.tourneyFile, name);
10079             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10080             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10081         }
10082         fclose(f);
10083         appData.noChessProgram = FALSE;
10084         appData.clockMode = TRUE;
10085         SetGNUMode();
10086         return 1;
10087 }
10088
10089 int
10090 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10091 {
10092     char buf[MSG_SIZ], *p, *q;
10093     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10094     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10095     skip = !all && group[0]; // if group requested, we start in skip mode
10096     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10097         p = names; q = buf; header = 0;
10098         while(*p && *p != '\n') *q++ = *p++;
10099         *q = 0;
10100         if(*p == '\n') p++;
10101         if(buf[0] == '#') {
10102             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10103             depth++; // we must be entering a new group
10104             if(all) continue; // suppress printing group headers when complete list requested
10105             header = 1;
10106             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10107         }
10108         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10109         if(engineList[i]) free(engineList[i]);
10110         engineList[i] = strdup(buf);
10111         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10112         if(engineMnemonic[i]) free(engineMnemonic[i]);
10113         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10114             strcat(buf, " (");
10115             sscanf(q + 8, "%s", buf + strlen(buf));
10116             strcat(buf, ")");
10117         }
10118         engineMnemonic[i] = strdup(buf);
10119         i++;
10120     }
10121     engineList[i] = engineMnemonic[i] = NULL;
10122     return i;
10123 }
10124
10125 // following implemented as macro to avoid type limitations
10126 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10127
10128 void
10129 SwapEngines (int n)
10130 {   // swap settings for first engine and other engine (so far only some selected options)
10131     int h;
10132     char *p;
10133     if(n == 0) return;
10134     SWAP(directory, p)
10135     SWAP(chessProgram, p)
10136     SWAP(isUCI, h)
10137     SWAP(hasOwnBookUCI, h)
10138     SWAP(protocolVersion, h)
10139     SWAP(reuse, h)
10140     SWAP(scoreIsAbsolute, h)
10141     SWAP(timeOdds, h)
10142     SWAP(logo, p)
10143     SWAP(pgnName, p)
10144     SWAP(pvSAN, h)
10145     SWAP(engOptions, p)
10146     SWAP(engInitString, p)
10147     SWAP(computerString, p)
10148     SWAP(features, p)
10149     SWAP(fenOverride, p)
10150     SWAP(NPS, h)
10151     SWAP(accumulateTC, h)
10152     SWAP(host, p)
10153 }
10154
10155 int
10156 SetPlayer (int player, char *p)
10157 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10158     int i;
10159     char buf[MSG_SIZ], *engineName;
10160     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10161     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10162     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10163     if(mnemonic[i]) {
10164         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10165         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10166         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10167         ParseArgsFromString(buf);
10168     }
10169     free(engineName);
10170     return i;
10171 }
10172
10173 char *recentEngines;
10174
10175 void
10176 RecentEngineEvent (int nr)
10177 {
10178     int n;
10179 //    SwapEngines(1); // bump first to second
10180 //    ReplaceEngine(&second, 1); // and load it there
10181     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10182     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10183     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10184         ReplaceEngine(&first, 0);
10185         FloatToFront(&appData.recentEngineList, command[n]);
10186     }
10187 }
10188
10189 int
10190 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10191 {   // determine players from game number
10192     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10193
10194     if(appData.tourneyType == 0) {
10195         roundsPerCycle = (nPlayers - 1) | 1;
10196         pairingsPerRound = nPlayers / 2;
10197     } else if(appData.tourneyType > 0) {
10198         roundsPerCycle = nPlayers - appData.tourneyType;
10199         pairingsPerRound = appData.tourneyType;
10200     }
10201     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10202     gamesPerCycle = gamesPerRound * roundsPerCycle;
10203     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10204     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10205     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10206     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10207     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10208     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10209
10210     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10211     if(appData.roundSync) *syncInterval = gamesPerRound;
10212
10213     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10214
10215     if(appData.tourneyType == 0) {
10216         if(curPairing == (nPlayers-1)/2 ) {
10217             *whitePlayer = curRound;
10218             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10219         } else {
10220             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10221             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10222             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10223             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10224         }
10225     } else if(appData.tourneyType > 1) {
10226         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10227         *whitePlayer = curRound + appData.tourneyType;
10228     } else if(appData.tourneyType > 0) {
10229         *whitePlayer = curPairing;
10230         *blackPlayer = curRound + appData.tourneyType;
10231     }
10232
10233     // take care of white/black alternation per round. 
10234     // For cycles and games this is already taken care of by default, derived from matchGame!
10235     return curRound & 1;
10236 }
10237
10238 int
10239 NextTourneyGame (int nr, int *swapColors)
10240 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10241     char *p, *q;
10242     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10243     FILE *tf;
10244     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10245     tf = fopen(appData.tourneyFile, "r");
10246     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10247     ParseArgsFromFile(tf); fclose(tf);
10248     InitTimeControls(); // TC might be altered from tourney file
10249
10250     nPlayers = CountPlayers(appData.participants); // count participants
10251     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10252     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10253
10254     if(syncInterval) {
10255         p = q = appData.results;
10256         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10257         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10258             DisplayMessage(_("Waiting for other game(s)"),"");
10259             waitingForGame = TRUE;
10260             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10261             return 0;
10262         }
10263         waitingForGame = FALSE;
10264     }
10265
10266     if(appData.tourneyType < 0) {
10267         if(nr>=0 && !pairingReceived) {
10268             char buf[1<<16];
10269             if(pairing.pr == NoProc) {
10270                 if(!appData.pairingEngine[0]) {
10271                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10272                     return 0;
10273                 }
10274                 StartChessProgram(&pairing); // starts the pairing engine
10275             }
10276             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10277             SendToProgram(buf, &pairing);
10278             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10279             SendToProgram(buf, &pairing);
10280             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10281         }
10282         pairingReceived = 0;                              // ... so we continue here 
10283         *swapColors = 0;
10284         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10285         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10286         matchGame = 1; roundNr = nr / syncInterval + 1;
10287     }
10288
10289     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10290
10291     // redefine engines, engine dir, etc.
10292     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10293     if(first.pr == NoProc) {
10294       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10295       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10296     }
10297     if(second.pr == NoProc) {
10298       SwapEngines(1);
10299       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10300       SwapEngines(1);         // and make that valid for second engine by swapping
10301       InitEngine(&second, 1);
10302     }
10303     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10304     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10305     return 1;
10306 }
10307
10308 void
10309 NextMatchGame ()
10310 {   // performs game initialization that does not invoke engines, and then tries to start the game
10311     int res, firstWhite, swapColors = 0;
10312     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10313     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
10314         char buf[MSG_SIZ];
10315         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10316         if(strcmp(buf, currentDebugFile)) { // name has changed
10317             FILE *f = fopen(buf, "w");
10318             if(f) { // if opening the new file failed, just keep using the old one
10319                 ASSIGN(currentDebugFile, buf);
10320                 fclose(debugFP);
10321                 debugFP = f;
10322             }
10323             if(appData.serverFileName) {
10324                 if(serverFP) fclose(serverFP);
10325                 serverFP = fopen(appData.serverFileName, "w");
10326                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10327                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10328             }
10329         }
10330     }
10331     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10332     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10333     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10334     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10335     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10336     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10337     Reset(FALSE, first.pr != NoProc);
10338     res = LoadGameOrPosition(matchGame); // setup game
10339     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10340     if(!res) return; // abort when bad game/pos file
10341     TwoMachinesEvent();
10342 }
10343
10344 void
10345 UserAdjudicationEvent (int result)
10346 {
10347     ChessMove gameResult = GameIsDrawn;
10348
10349     if( result > 0 ) {
10350         gameResult = WhiteWins;
10351     }
10352     else if( result < 0 ) {
10353         gameResult = BlackWins;
10354     }
10355
10356     if( gameMode == TwoMachinesPlay ) {
10357         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10358     }
10359 }
10360
10361
10362 // [HGM] save: calculate checksum of game to make games easily identifiable
10363 int
10364 StringCheckSum (char *s)
10365 {
10366         int i = 0;
10367         if(s==NULL) return 0;
10368         while(*s) i = i*259 + *s++;
10369         return i;
10370 }
10371
10372 int
10373 GameCheckSum ()
10374 {
10375         int i, sum=0;
10376         for(i=backwardMostMove; i<forwardMostMove; i++) {
10377                 sum += pvInfoList[i].depth;
10378                 sum += StringCheckSum(parseList[i]);
10379                 sum += StringCheckSum(commentList[i]);
10380                 sum *= 261;
10381         }
10382         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10383         return sum + StringCheckSum(commentList[i]);
10384 } // end of save patch
10385
10386 void
10387 GameEnds (ChessMove result, char *resultDetails, int whosays)
10388 {
10389     GameMode nextGameMode;
10390     int isIcsGame;
10391     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10392
10393     if(endingGame) return; /* [HGM] crash: forbid recursion */
10394     endingGame = 1;
10395     if(twoBoards) { // [HGM] dual: switch back to one board
10396         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10397         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10398     }
10399     if (appData.debugMode) {
10400       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10401               result, resultDetails ? resultDetails : "(null)", whosays);
10402     }
10403
10404     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10405
10406     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10407         /* If we are playing on ICS, the server decides when the
10408            game is over, but the engine can offer to draw, claim
10409            a draw, or resign.
10410          */
10411 #if ZIPPY
10412         if (appData.zippyPlay && first.initDone) {
10413             if (result == GameIsDrawn) {
10414                 /* In case draw still needs to be claimed */
10415                 SendToICS(ics_prefix);
10416                 SendToICS("draw\n");
10417             } else if (StrCaseStr(resultDetails, "resign")) {
10418                 SendToICS(ics_prefix);
10419                 SendToICS("resign\n");
10420             }
10421         }
10422 #endif
10423         endingGame = 0; /* [HGM] crash */
10424         return;
10425     }
10426
10427     /* If we're loading the game from a file, stop */
10428     if (whosays == GE_FILE) {
10429       (void) StopLoadGameTimer();
10430       gameFileFP = NULL;
10431     }
10432
10433     /* Cancel draw offers */
10434     first.offeredDraw = second.offeredDraw = 0;
10435
10436     /* If this is an ICS game, only ICS can really say it's done;
10437        if not, anyone can. */
10438     isIcsGame = (gameMode == IcsPlayingWhite ||
10439                  gameMode == IcsPlayingBlack ||
10440                  gameMode == IcsObserving    ||
10441                  gameMode == IcsExamining);
10442
10443     if (!isIcsGame || whosays == GE_ICS) {
10444         /* OK -- not an ICS game, or ICS said it was done */
10445         StopClocks();
10446         if (!isIcsGame && !appData.noChessProgram)
10447           SetUserThinkingEnables();
10448
10449         /* [HGM] if a machine claims the game end we verify this claim */
10450         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10451             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10452                 char claimer;
10453                 ChessMove trueResult = (ChessMove) -1;
10454
10455                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10456                                             first.twoMachinesColor[0] :
10457                                             second.twoMachinesColor[0] ;
10458
10459                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10460                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10461                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10462                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10463                 } else
10464                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10465                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10466                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10467                 } else
10468                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10469                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10470                 }
10471
10472                 // now verify win claims, but not in drop games, as we don't understand those yet
10473                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10474                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10475                     (result == WhiteWins && claimer == 'w' ||
10476                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10477                       if (appData.debugMode) {
10478                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10479                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10480                       }
10481                       if(result != trueResult) {
10482                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10483                               result = claimer == 'w' ? BlackWins : WhiteWins;
10484                               resultDetails = buf;
10485                       }
10486                 } else
10487                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10488                     && (forwardMostMove <= backwardMostMove ||
10489                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10490                         (claimer=='b')==(forwardMostMove&1))
10491                                                                                   ) {
10492                       /* [HGM] verify: draws that were not flagged are false claims */
10493                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10494                       result = claimer == 'w' ? BlackWins : WhiteWins;
10495                       resultDetails = buf;
10496                 }
10497                 /* (Claiming a loss is accepted no questions asked!) */
10498             }
10499             /* [HGM] bare: don't allow bare King to win */
10500             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10501                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10502                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10503                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10504                && result != GameIsDrawn)
10505             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10506                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10507                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10508                         if(p >= 0 && p <= (int)WhiteKing) k++;
10509                 }
10510                 if (appData.debugMode) {
10511                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10512                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10513                 }
10514                 if(k <= 1) {
10515                         result = GameIsDrawn;
10516                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10517                         resultDetails = buf;
10518                 }
10519             }
10520         }
10521
10522
10523         if(serverMoves != NULL && !loadFlag) { char c = '=';
10524             if(result==WhiteWins) c = '+';
10525             if(result==BlackWins) c = '-';
10526             if(resultDetails != NULL)
10527                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10528         }
10529         if (resultDetails != NULL) {
10530             gameInfo.result = result;
10531             gameInfo.resultDetails = StrSave(resultDetails);
10532
10533             /* display last move only if game was not loaded from file */
10534             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10535                 DisplayMove(currentMove - 1);
10536
10537             if (forwardMostMove != 0) {
10538                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10539                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10540                                                                 ) {
10541                     if (*appData.saveGameFile != NULLCHAR) {
10542                         SaveGameToFile(appData.saveGameFile, TRUE);
10543                     } else if (appData.autoSaveGames) {
10544                         AutoSaveGame();
10545                     }
10546                     if (*appData.savePositionFile != NULLCHAR) {
10547                         SavePositionToFile(appData.savePositionFile);
10548                     }
10549                 }
10550             }
10551
10552             /* Tell program how game ended in case it is learning */
10553             /* [HGM] Moved this to after saving the PGN, just in case */
10554             /* engine died and we got here through time loss. In that */
10555             /* case we will get a fatal error writing the pipe, which */
10556             /* would otherwise lose us the PGN.                       */
10557             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10558             /* output during GameEnds should never be fatal anymore   */
10559             if (gameMode == MachinePlaysWhite ||
10560                 gameMode == MachinePlaysBlack ||
10561                 gameMode == TwoMachinesPlay ||
10562                 gameMode == IcsPlayingWhite ||
10563                 gameMode == IcsPlayingBlack ||
10564                 gameMode == BeginningOfGame) {
10565                 char buf[MSG_SIZ];
10566                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10567                         resultDetails);
10568                 if (first.pr != NoProc) {
10569                     SendToProgram(buf, &first);
10570                 }
10571                 if (second.pr != NoProc &&
10572                     gameMode == TwoMachinesPlay) {
10573                     SendToProgram(buf, &second);
10574                 }
10575             }
10576         }
10577
10578         if (appData.icsActive) {
10579             if (appData.quietPlay &&
10580                 (gameMode == IcsPlayingWhite ||
10581                  gameMode == IcsPlayingBlack)) {
10582                 SendToICS(ics_prefix);
10583                 SendToICS("set shout 1\n");
10584             }
10585             nextGameMode = IcsIdle;
10586             ics_user_moved = FALSE;
10587             /* clean up premove.  It's ugly when the game has ended and the
10588              * premove highlights are still on the board.
10589              */
10590             if (gotPremove) {
10591               gotPremove = FALSE;
10592               ClearPremoveHighlights();
10593               DrawPosition(FALSE, boards[currentMove]);
10594             }
10595             if (whosays == GE_ICS) {
10596                 switch (result) {
10597                 case WhiteWins:
10598                     if (gameMode == IcsPlayingWhite)
10599                         PlayIcsWinSound();
10600                     else if(gameMode == IcsPlayingBlack)
10601                         PlayIcsLossSound();
10602                     break;
10603                 case BlackWins:
10604                     if (gameMode == IcsPlayingBlack)
10605                         PlayIcsWinSound();
10606                     else if(gameMode == IcsPlayingWhite)
10607                         PlayIcsLossSound();
10608                     break;
10609                 case GameIsDrawn:
10610                     PlayIcsDrawSound();
10611                     break;
10612                 default:
10613                     PlayIcsUnfinishedSound();
10614                 }
10615             }
10616         } else if (gameMode == EditGame ||
10617                    gameMode == PlayFromGameFile ||
10618                    gameMode == AnalyzeMode ||
10619                    gameMode == AnalyzeFile) {
10620             nextGameMode = gameMode;
10621         } else {
10622             nextGameMode = EndOfGame;
10623         }
10624         pausing = FALSE;
10625         ModeHighlight();
10626     } else {
10627         nextGameMode = gameMode;
10628     }
10629
10630     if (appData.noChessProgram) {
10631         gameMode = nextGameMode;
10632         ModeHighlight();
10633         endingGame = 0; /* [HGM] crash */
10634         return;
10635     }
10636
10637     if (first.reuse) {
10638         /* Put first chess program into idle state */
10639         if (first.pr != NoProc &&
10640             (gameMode == MachinePlaysWhite ||
10641              gameMode == MachinePlaysBlack ||
10642              gameMode == TwoMachinesPlay ||
10643              gameMode == IcsPlayingWhite ||
10644              gameMode == IcsPlayingBlack ||
10645              gameMode == BeginningOfGame)) {
10646             SendToProgram("force\n", &first);
10647             if (first.usePing) {
10648               char buf[MSG_SIZ];
10649               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10650               SendToProgram(buf, &first);
10651             }
10652         }
10653     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10654         /* Kill off first chess program */
10655         if (first.isr != NULL)
10656           RemoveInputSource(first.isr);
10657         first.isr = NULL;
10658
10659         if (first.pr != NoProc) {
10660             ExitAnalyzeMode();
10661             DoSleep( appData.delayBeforeQuit );
10662             SendToProgram("quit\n", &first);
10663             DoSleep( appData.delayAfterQuit );
10664             DestroyChildProcess(first.pr, first.useSigterm);
10665         }
10666         first.pr = NoProc;
10667     }
10668     if (second.reuse) {
10669         /* Put second chess program into idle state */
10670         if (second.pr != NoProc &&
10671             gameMode == TwoMachinesPlay) {
10672             SendToProgram("force\n", &second);
10673             if (second.usePing) {
10674               char buf[MSG_SIZ];
10675               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10676               SendToProgram(buf, &second);
10677             }
10678         }
10679     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10680         /* Kill off second chess program */
10681         if (second.isr != NULL)
10682           RemoveInputSource(second.isr);
10683         second.isr = NULL;
10684
10685         if (second.pr != NoProc) {
10686             DoSleep( appData.delayBeforeQuit );
10687             SendToProgram("quit\n", &second);
10688             DoSleep( appData.delayAfterQuit );
10689             DestroyChildProcess(second.pr, second.useSigterm);
10690         }
10691         second.pr = NoProc;
10692     }
10693
10694     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10695         char resChar = '=';
10696         switch (result) {
10697         case WhiteWins:
10698           resChar = '+';
10699           if (first.twoMachinesColor[0] == 'w') {
10700             first.matchWins++;
10701           } else {
10702             second.matchWins++;
10703           }
10704           break;
10705         case BlackWins:
10706           resChar = '-';
10707           if (first.twoMachinesColor[0] == 'b') {
10708             first.matchWins++;
10709           } else {
10710             second.matchWins++;
10711           }
10712           break;
10713         case GameUnfinished:
10714           resChar = ' ';
10715         default:
10716           break;
10717         }
10718
10719         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10720         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10721             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10722             ReserveGame(nextGame, resChar); // sets nextGame
10723             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10724             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10725         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10726
10727         if (nextGame <= appData.matchGames && !abortMatch) {
10728             gameMode = nextGameMode;
10729             matchGame = nextGame; // this will be overruled in tourney mode!
10730             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10731             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10732             endingGame = 0; /* [HGM] crash */
10733             return;
10734         } else {
10735             gameMode = nextGameMode;
10736             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10737                      first.tidy, second.tidy,
10738                      first.matchWins, second.matchWins,
10739                      appData.matchGames - (first.matchWins + second.matchWins));
10740             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10741             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10742             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10743             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10744                 first.twoMachinesColor = "black\n";
10745                 second.twoMachinesColor = "white\n";
10746             } else {
10747                 first.twoMachinesColor = "white\n";
10748                 second.twoMachinesColor = "black\n";
10749             }
10750         }
10751     }
10752     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10753         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10754       ExitAnalyzeMode();
10755     gameMode = nextGameMode;
10756     ModeHighlight();
10757     endingGame = 0;  /* [HGM] crash */
10758     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10759         if(matchMode == TRUE) { // match through command line: exit with or without popup
10760             if(ranking) {
10761                 ToNrEvent(forwardMostMove);
10762                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10763                 else ExitEvent(0);
10764             } else DisplayFatalError(buf, 0, 0);
10765         } else { // match through menu; just stop, with or without popup
10766             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10767             ModeHighlight();
10768             if(ranking){
10769                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10770             } else DisplayNote(buf);
10771       }
10772       if(ranking) free(ranking);
10773     }
10774 }
10775
10776 /* Assumes program was just initialized (initString sent).
10777    Leaves program in force mode. */
10778 void
10779 FeedMovesToProgram (ChessProgramState *cps, int upto)
10780 {
10781     int i;
10782
10783     if (appData.debugMode)
10784       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10785               startedFromSetupPosition ? "position and " : "",
10786               backwardMostMove, upto, cps->which);
10787     if(currentlyInitializedVariant != gameInfo.variant) {
10788       char buf[MSG_SIZ];
10789         // [HGM] variantswitch: make engine aware of new variant
10790         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10791                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10792         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10793         SendToProgram(buf, cps);
10794         currentlyInitializedVariant = gameInfo.variant;
10795     }
10796     SendToProgram("force\n", cps);
10797     if (startedFromSetupPosition) {
10798         SendBoard(cps, backwardMostMove);
10799     if (appData.debugMode) {
10800         fprintf(debugFP, "feedMoves\n");
10801     }
10802     }
10803     for (i = backwardMostMove; i < upto; i++) {
10804         SendMoveToProgram(i, cps);
10805     }
10806 }
10807
10808
10809 int
10810 ResurrectChessProgram ()
10811 {
10812      /* The chess program may have exited.
10813         If so, restart it and feed it all the moves made so far. */
10814     static int doInit = 0;
10815
10816     if (appData.noChessProgram) return 1;
10817
10818     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10819         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10820         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10821         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10822     } else {
10823         if (first.pr != NoProc) return 1;
10824         StartChessProgram(&first);
10825     }
10826     InitChessProgram(&first, FALSE);
10827     FeedMovesToProgram(&first, currentMove);
10828
10829     if (!first.sendTime) {
10830         /* can't tell gnuchess what its clock should read,
10831            so we bow to its notion. */
10832         ResetClocks();
10833         timeRemaining[0][currentMove] = whiteTimeRemaining;
10834         timeRemaining[1][currentMove] = blackTimeRemaining;
10835     }
10836
10837     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10838                 appData.icsEngineAnalyze) && first.analysisSupport) {
10839       SendToProgram("analyze\n", &first);
10840       first.analyzing = TRUE;
10841     }
10842     return 1;
10843 }
10844
10845 /*
10846  * Button procedures
10847  */
10848 void
10849 Reset (int redraw, int init)
10850 {
10851     int i;
10852
10853     if (appData.debugMode) {
10854         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10855                 redraw, init, gameMode);
10856     }
10857     CleanupTail(); // [HGM] vari: delete any stored variations
10858     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10859     pausing = pauseExamInvalid = FALSE;
10860     startedFromSetupPosition = blackPlaysFirst = FALSE;
10861     firstMove = TRUE;
10862     whiteFlag = blackFlag = FALSE;
10863     userOfferedDraw = FALSE;
10864     hintRequested = bookRequested = FALSE;
10865     first.maybeThinking = FALSE;
10866     second.maybeThinking = FALSE;
10867     first.bookSuspend = FALSE; // [HGM] book
10868     second.bookSuspend = FALSE;
10869     thinkOutput[0] = NULLCHAR;
10870     lastHint[0] = NULLCHAR;
10871     ClearGameInfo(&gameInfo);
10872     gameInfo.variant = StringToVariant(appData.variant);
10873     ics_user_moved = ics_clock_paused = FALSE;
10874     ics_getting_history = H_FALSE;
10875     ics_gamenum = -1;
10876     white_holding[0] = black_holding[0] = NULLCHAR;
10877     ClearProgramStats();
10878     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10879
10880     ResetFrontEnd();
10881     ClearHighlights();
10882     flipView = appData.flipView;
10883     ClearPremoveHighlights();
10884     gotPremove = FALSE;
10885     alarmSounded = FALSE;
10886
10887     GameEnds(EndOfFile, NULL, GE_PLAYER);
10888     if(appData.serverMovesName != NULL) {
10889         /* [HGM] prepare to make moves file for broadcasting */
10890         clock_t t = clock();
10891         if(serverMoves != NULL) fclose(serverMoves);
10892         serverMoves = fopen(appData.serverMovesName, "r");
10893         if(serverMoves != NULL) {
10894             fclose(serverMoves);
10895             /* delay 15 sec before overwriting, so all clients can see end */
10896             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10897         }
10898         serverMoves = fopen(appData.serverMovesName, "w");
10899     }
10900
10901     ExitAnalyzeMode();
10902     gameMode = BeginningOfGame;
10903     ModeHighlight();
10904     if(appData.icsActive) gameInfo.variant = VariantNormal;
10905     currentMove = forwardMostMove = backwardMostMove = 0;
10906     MarkTargetSquares(1);
10907     InitPosition(redraw);
10908     for (i = 0; i < MAX_MOVES; i++) {
10909         if (commentList[i] != NULL) {
10910             free(commentList[i]);
10911             commentList[i] = NULL;
10912         }
10913     }
10914     ResetClocks();
10915     timeRemaining[0][0] = whiteTimeRemaining;
10916     timeRemaining[1][0] = blackTimeRemaining;
10917
10918     if (first.pr == NoProc) {
10919         StartChessProgram(&first);
10920     }
10921     if (init) {
10922             InitChessProgram(&first, startedFromSetupPosition);
10923     }
10924     DisplayTitle("");
10925     DisplayMessage("", "");
10926     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10927     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10928     ClearMap();        // [HGM] exclude: invalidate map
10929 }
10930
10931 void
10932 AutoPlayGameLoop ()
10933 {
10934     for (;;) {
10935         if (!AutoPlayOneMove())
10936           return;
10937         if (matchMode || appData.timeDelay == 0)
10938           continue;
10939         if (appData.timeDelay < 0)
10940           return;
10941         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
10942         break;
10943     }
10944 }
10945
10946
10947 int
10948 AutoPlayOneMove ()
10949 {
10950     int fromX, fromY, toX, toY;
10951
10952     if (appData.debugMode) {
10953       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10954     }
10955
10956     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10957       return FALSE;
10958
10959     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10960       pvInfoList[currentMove].depth = programStats.depth;
10961       pvInfoList[currentMove].score = programStats.score;
10962       pvInfoList[currentMove].time  = 0;
10963       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10964     }
10965
10966     if (currentMove >= forwardMostMove) {
10967       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10968 //      gameMode = EndOfGame;
10969 //      ModeHighlight();
10970
10971       /* [AS] Clear current move marker at the end of a game */
10972       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10973
10974       return FALSE;
10975     }
10976
10977     toX = moveList[currentMove][2] - AAA;
10978     toY = moveList[currentMove][3] - ONE;
10979
10980     if (moveList[currentMove][1] == '@') {
10981         if (appData.highlightLastMove) {
10982             SetHighlights(-1, -1, toX, toY);
10983         }
10984     } else {
10985         fromX = moveList[currentMove][0] - AAA;
10986         fromY = moveList[currentMove][1] - ONE;
10987
10988         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10989
10990         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10991
10992         if (appData.highlightLastMove) {
10993             SetHighlights(fromX, fromY, toX, toY);
10994         }
10995     }
10996     DisplayMove(currentMove);
10997     SendMoveToProgram(currentMove++, &first);
10998     DisplayBothClocks();
10999     DrawPosition(FALSE, boards[currentMove]);
11000     // [HGM] PV info: always display, routine tests if empty
11001     DisplayComment(currentMove - 1, commentList[currentMove]);
11002     return TRUE;
11003 }
11004
11005
11006 int
11007 LoadGameOneMove (ChessMove readAhead)
11008 {
11009     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11010     char promoChar = NULLCHAR;
11011     ChessMove moveType;
11012     char move[MSG_SIZ];
11013     char *p, *q;
11014
11015     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11016         gameMode != AnalyzeMode && gameMode != Training) {
11017         gameFileFP = NULL;
11018         return FALSE;
11019     }
11020
11021     yyboardindex = forwardMostMove;
11022     if (readAhead != EndOfFile) {
11023       moveType = readAhead;
11024     } else {
11025       if (gameFileFP == NULL)
11026           return FALSE;
11027       moveType = (ChessMove) Myylex();
11028     }
11029
11030     done = FALSE;
11031     switch (moveType) {
11032       case Comment:
11033         if (appData.debugMode)
11034           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11035         p = yy_text;
11036
11037         /* append the comment but don't display it */
11038         AppendComment(currentMove, p, FALSE);
11039         return TRUE;
11040
11041       case WhiteCapturesEnPassant:
11042       case BlackCapturesEnPassant:
11043       case WhitePromotion:
11044       case BlackPromotion:
11045       case WhiteNonPromotion:
11046       case BlackNonPromotion:
11047       case NormalMove:
11048       case WhiteKingSideCastle:
11049       case WhiteQueenSideCastle:
11050       case BlackKingSideCastle:
11051       case BlackQueenSideCastle:
11052       case WhiteKingSideCastleWild:
11053       case WhiteQueenSideCastleWild:
11054       case BlackKingSideCastleWild:
11055       case BlackQueenSideCastleWild:
11056       /* PUSH Fabien */
11057       case WhiteHSideCastleFR:
11058       case WhiteASideCastleFR:
11059       case BlackHSideCastleFR:
11060       case BlackASideCastleFR:
11061       /* POP Fabien */
11062         if (appData.debugMode)
11063           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11064         fromX = currentMoveString[0] - AAA;
11065         fromY = currentMoveString[1] - ONE;
11066         toX = currentMoveString[2] - AAA;
11067         toY = currentMoveString[3] - ONE;
11068         promoChar = currentMoveString[4];
11069         break;
11070
11071       case WhiteDrop:
11072       case BlackDrop:
11073         if (appData.debugMode)
11074           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11075         fromX = moveType == WhiteDrop ?
11076           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11077         (int) CharToPiece(ToLower(currentMoveString[0]));
11078         fromY = DROP_RANK;
11079         toX = currentMoveString[2] - AAA;
11080         toY = currentMoveString[3] - ONE;
11081         break;
11082
11083       case WhiteWins:
11084       case BlackWins:
11085       case GameIsDrawn:
11086       case GameUnfinished:
11087         if (appData.debugMode)
11088           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11089         p = strchr(yy_text, '{');
11090         if (p == NULL) p = strchr(yy_text, '(');
11091         if (p == NULL) {
11092             p = yy_text;
11093             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11094         } else {
11095             q = strchr(p, *p == '{' ? '}' : ')');
11096             if (q != NULL) *q = NULLCHAR;
11097             p++;
11098         }
11099         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11100         GameEnds(moveType, p, GE_FILE);
11101         done = TRUE;
11102         if (cmailMsgLoaded) {
11103             ClearHighlights();
11104             flipView = WhiteOnMove(currentMove);
11105             if (moveType == GameUnfinished) flipView = !flipView;
11106             if (appData.debugMode)
11107               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11108         }
11109         break;
11110
11111       case EndOfFile:
11112         if (appData.debugMode)
11113           fprintf(debugFP, "Parser hit end of file\n");
11114         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11115           case MT_NONE:
11116           case MT_CHECK:
11117             break;
11118           case MT_CHECKMATE:
11119           case MT_STAINMATE:
11120             if (WhiteOnMove(currentMove)) {
11121                 GameEnds(BlackWins, "Black mates", GE_FILE);
11122             } else {
11123                 GameEnds(WhiteWins, "White mates", GE_FILE);
11124             }
11125             break;
11126           case MT_STALEMATE:
11127             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11128             break;
11129         }
11130         done = TRUE;
11131         break;
11132
11133       case MoveNumberOne:
11134         if (lastLoadGameStart == GNUChessGame) {
11135             /* GNUChessGames have numbers, but they aren't move numbers */
11136             if (appData.debugMode)
11137               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11138                       yy_text, (int) moveType);
11139             return LoadGameOneMove(EndOfFile); /* tail recursion */
11140         }
11141         /* else fall thru */
11142
11143       case XBoardGame:
11144       case GNUChessGame:
11145       case PGNTag:
11146         /* Reached start of next game in file */
11147         if (appData.debugMode)
11148           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11149         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11150           case MT_NONE:
11151           case MT_CHECK:
11152             break;
11153           case MT_CHECKMATE:
11154           case MT_STAINMATE:
11155             if (WhiteOnMove(currentMove)) {
11156                 GameEnds(BlackWins, "Black mates", GE_FILE);
11157             } else {
11158                 GameEnds(WhiteWins, "White mates", GE_FILE);
11159             }
11160             break;
11161           case MT_STALEMATE:
11162             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11163             break;
11164         }
11165         done = TRUE;
11166         break;
11167
11168       case PositionDiagram:     /* should not happen; ignore */
11169       case ElapsedTime:         /* ignore */
11170       case NAG:                 /* ignore */
11171         if (appData.debugMode)
11172           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11173                   yy_text, (int) moveType);
11174         return LoadGameOneMove(EndOfFile); /* tail recursion */
11175
11176       case IllegalMove:
11177         if (appData.testLegality) {
11178             if (appData.debugMode)
11179               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11180             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11181                     (forwardMostMove / 2) + 1,
11182                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11183             DisplayError(move, 0);
11184             done = TRUE;
11185         } else {
11186             if (appData.debugMode)
11187               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11188                       yy_text, currentMoveString);
11189             fromX = currentMoveString[0] - AAA;
11190             fromY = currentMoveString[1] - ONE;
11191             toX = currentMoveString[2] - AAA;
11192             toY = currentMoveString[3] - ONE;
11193             promoChar = currentMoveString[4];
11194         }
11195         break;
11196
11197       case AmbiguousMove:
11198         if (appData.debugMode)
11199           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11200         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11201                 (forwardMostMove / 2) + 1,
11202                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11203         DisplayError(move, 0);
11204         done = TRUE;
11205         break;
11206
11207       default:
11208       case ImpossibleMove:
11209         if (appData.debugMode)
11210           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11211         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11212                 (forwardMostMove / 2) + 1,
11213                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11214         DisplayError(move, 0);
11215         done = TRUE;
11216         break;
11217     }
11218
11219     if (done) {
11220         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11221             DrawPosition(FALSE, boards[currentMove]);
11222             DisplayBothClocks();
11223             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11224               DisplayComment(currentMove - 1, commentList[currentMove]);
11225         }
11226         (void) StopLoadGameTimer();
11227         gameFileFP = NULL;
11228         cmailOldMove = forwardMostMove;
11229         return FALSE;
11230     } else {
11231         /* currentMoveString is set as a side-effect of yylex */
11232
11233         thinkOutput[0] = NULLCHAR;
11234         MakeMove(fromX, fromY, toX, toY, promoChar);
11235         currentMove = forwardMostMove;
11236         return TRUE;
11237     }
11238 }
11239
11240 /* Load the nth game from the given file */
11241 int
11242 LoadGameFromFile (char *filename, int n, char *title, int useList)
11243 {
11244     FILE *f;
11245     char buf[MSG_SIZ];
11246
11247     if (strcmp(filename, "-") == 0) {
11248         f = stdin;
11249         title = "stdin";
11250     } else {
11251         f = fopen(filename, "rb");
11252         if (f == NULL) {
11253           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11254             DisplayError(buf, errno);
11255             return FALSE;
11256         }
11257     }
11258     if (fseek(f, 0, 0) == -1) {
11259         /* f is not seekable; probably a pipe */
11260         useList = FALSE;
11261     }
11262     if (useList && n == 0) {
11263         int error = GameListBuild(f);
11264         if (error) {
11265             DisplayError(_("Cannot build game list"), error);
11266         } else if (!ListEmpty(&gameList) &&
11267                    ((ListGame *) gameList.tailPred)->number > 1) {
11268             GameListPopUp(f, title);
11269             return TRUE;
11270         }
11271         GameListDestroy();
11272         n = 1;
11273     }
11274     if (n == 0) n = 1;
11275     return LoadGame(f, n, title, FALSE);
11276 }
11277
11278
11279 void
11280 MakeRegisteredMove ()
11281 {
11282     int fromX, fromY, toX, toY;
11283     char promoChar;
11284     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11285         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11286           case CMAIL_MOVE:
11287           case CMAIL_DRAW:
11288             if (appData.debugMode)
11289               fprintf(debugFP, "Restoring %s for game %d\n",
11290                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11291
11292             thinkOutput[0] = NULLCHAR;
11293             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11294             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11295             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11296             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11297             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11298             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11299             MakeMove(fromX, fromY, toX, toY, promoChar);
11300             ShowMove(fromX, fromY, toX, toY);
11301
11302             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11303               case MT_NONE:
11304               case MT_CHECK:
11305                 break;
11306
11307               case MT_CHECKMATE:
11308               case MT_STAINMATE:
11309                 if (WhiteOnMove(currentMove)) {
11310                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11311                 } else {
11312                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11313                 }
11314                 break;
11315
11316               case MT_STALEMATE:
11317                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11318                 break;
11319             }
11320
11321             break;
11322
11323           case CMAIL_RESIGN:
11324             if (WhiteOnMove(currentMove)) {
11325                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11326             } else {
11327                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11328             }
11329             break;
11330
11331           case CMAIL_ACCEPT:
11332             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11333             break;
11334
11335           default:
11336             break;
11337         }
11338     }
11339
11340     return;
11341 }
11342
11343 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11344 int
11345 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11346 {
11347     int retVal;
11348
11349     if (gameNumber > nCmailGames) {
11350         DisplayError(_("No more games in this message"), 0);
11351         return FALSE;
11352     }
11353     if (f == lastLoadGameFP) {
11354         int offset = gameNumber - lastLoadGameNumber;
11355         if (offset == 0) {
11356             cmailMsg[0] = NULLCHAR;
11357             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11358                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11359                 nCmailMovesRegistered--;
11360             }
11361             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11362             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11363                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11364             }
11365         } else {
11366             if (! RegisterMove()) return FALSE;
11367         }
11368     }
11369
11370     retVal = LoadGame(f, gameNumber, title, useList);
11371
11372     /* Make move registered during previous look at this game, if any */
11373     MakeRegisteredMove();
11374
11375     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11376         commentList[currentMove]
11377           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11378         DisplayComment(currentMove - 1, commentList[currentMove]);
11379     }
11380
11381     return retVal;
11382 }
11383
11384 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11385 int
11386 ReloadGame (int offset)
11387 {
11388     int gameNumber = lastLoadGameNumber + offset;
11389     if (lastLoadGameFP == NULL) {
11390         DisplayError(_("No game has been loaded yet"), 0);
11391         return FALSE;
11392     }
11393     if (gameNumber <= 0) {
11394         DisplayError(_("Can't back up any further"), 0);
11395         return FALSE;
11396     }
11397     if (cmailMsgLoaded) {
11398         return CmailLoadGame(lastLoadGameFP, gameNumber,
11399                              lastLoadGameTitle, lastLoadGameUseList);
11400     } else {
11401         return LoadGame(lastLoadGameFP, gameNumber,
11402                         lastLoadGameTitle, lastLoadGameUseList);
11403     }
11404 }
11405
11406 int keys[EmptySquare+1];
11407
11408 int
11409 PositionMatches (Board b1, Board b2)
11410 {
11411     int r, f, sum=0;
11412     switch(appData.searchMode) {
11413         case 1: return CompareWithRights(b1, b2);
11414         case 2:
11415             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11416                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11417             }
11418             return TRUE;
11419         case 3:
11420             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11421               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11422                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11423             }
11424             return sum==0;
11425         case 4:
11426             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11427                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11428             }
11429             return sum==0;
11430     }
11431     return TRUE;
11432 }
11433
11434 #define Q_PROMO  4
11435 #define Q_EP     3
11436 #define Q_BCASTL 2
11437 #define Q_WCASTL 1
11438
11439 int pieceList[256], quickBoard[256];
11440 ChessSquare pieceType[256] = { EmptySquare };
11441 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11442 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11443 int soughtTotal, turn;
11444 Boolean epOK, flipSearch;
11445
11446 typedef struct {
11447     unsigned char piece, to;
11448 } Move;
11449
11450 #define DSIZE (250000)
11451
11452 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11453 Move *moveDatabase = initialSpace;
11454 unsigned int movePtr, dataSize = DSIZE;
11455
11456 int
11457 MakePieceList (Board board, int *counts)
11458 {
11459     int r, f, n=Q_PROMO, total=0;
11460     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11461     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11462         int sq = f + (r<<4);
11463         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11464             quickBoard[sq] = ++n;
11465             pieceList[n] = sq;
11466             pieceType[n] = board[r][f];
11467             counts[board[r][f]]++;
11468             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11469             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11470             total++;
11471         }
11472     }
11473     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11474     return total;
11475 }
11476
11477 void
11478 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11479 {
11480     int sq = fromX + (fromY<<4);
11481     int piece = quickBoard[sq];
11482     quickBoard[sq] = 0;
11483     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11484     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11485         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11486         moveDatabase[movePtr++].piece = Q_WCASTL;
11487         quickBoard[sq] = piece;
11488         piece = quickBoard[from]; quickBoard[from] = 0;
11489         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11490     } else
11491     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11492         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11493         moveDatabase[movePtr++].piece = Q_BCASTL;
11494         quickBoard[sq] = piece;
11495         piece = quickBoard[from]; quickBoard[from] = 0;
11496         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11497     } else
11498     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11499         quickBoard[(fromY<<4)+toX] = 0;
11500         moveDatabase[movePtr].piece = Q_EP;
11501         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11502         moveDatabase[movePtr].to = sq;
11503     } else
11504     if(promoPiece != pieceType[piece]) {
11505         moveDatabase[movePtr++].piece = Q_PROMO;
11506         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11507     }
11508     moveDatabase[movePtr].piece = piece;
11509     quickBoard[sq] = piece;
11510     movePtr++;
11511 }
11512
11513 int
11514 PackGame (Board board)
11515 {
11516     Move *newSpace = NULL;
11517     moveDatabase[movePtr].piece = 0; // terminate previous game
11518     if(movePtr > dataSize) {
11519         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11520         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11521         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11522         if(newSpace) {
11523             int i;
11524             Move *p = moveDatabase, *q = newSpace;
11525             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11526             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11527             moveDatabase = newSpace;
11528         } else { // calloc failed, we must be out of memory. Too bad...
11529             dataSize = 0; // prevent calloc events for all subsequent games
11530             return 0;     // and signal this one isn't cached
11531         }
11532     }
11533     movePtr++;
11534     MakePieceList(board, counts);
11535     return movePtr;
11536 }
11537
11538 int
11539 QuickCompare (Board board, int *minCounts, int *maxCounts)
11540 {   // compare according to search mode
11541     int r, f;
11542     switch(appData.searchMode)
11543     {
11544       case 1: // exact position match
11545         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11546         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11547             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11548         }
11549         break;
11550       case 2: // can have extra material on empty squares
11551         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11552             if(board[r][f] == EmptySquare) continue;
11553             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11554         }
11555         break;
11556       case 3: // material with exact Pawn structure
11557         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11558             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11559             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11560         } // fall through to material comparison
11561       case 4: // exact material
11562         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11563         break;
11564       case 6: // material range with given imbalance
11565         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11566         // fall through to range comparison
11567       case 5: // material range
11568         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11569     }
11570     return TRUE;
11571 }
11572
11573 int
11574 QuickScan (Board board, Move *move)
11575 {   // reconstruct game,and compare all positions in it
11576     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11577     do {
11578         int piece = move->piece;
11579         int to = move->to, from = pieceList[piece];
11580         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11581           if(!piece) return -1;
11582           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11583             piece = (++move)->piece;
11584             from = pieceList[piece];
11585             counts[pieceType[piece]]--;
11586             pieceType[piece] = (ChessSquare) move->to;
11587             counts[move->to]++;
11588           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11589             counts[pieceType[quickBoard[to]]]--;
11590             quickBoard[to] = 0; total--;
11591             move++;
11592             continue;
11593           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11594             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11595             from  = pieceList[piece]; // so this must be King
11596             quickBoard[from] = 0;
11597             pieceList[piece] = to;
11598             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11599             quickBoard[from] = 0; // rook
11600             quickBoard[to] = piece;
11601             to = move->to; piece = move->piece;
11602             goto aftercastle;
11603           }
11604         }
11605         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11606         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11607         quickBoard[from] = 0;
11608       aftercastle:
11609         quickBoard[to] = piece;
11610         pieceList[piece] = to;
11611         cnt++; turn ^= 3;
11612         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11613            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11614            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11615                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11616           ) {
11617             static int lastCounts[EmptySquare+1];
11618             int i;
11619             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11620             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11621         } else stretch = 0;
11622         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11623         move++;
11624     } while(1);
11625 }
11626
11627 void
11628 InitSearch ()
11629 {
11630     int r, f;
11631     flipSearch = FALSE;
11632     CopyBoard(soughtBoard, boards[currentMove]);
11633     soughtTotal = MakePieceList(soughtBoard, maxSought);
11634     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11635     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11636     CopyBoard(reverseBoard, boards[currentMove]);
11637     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11638         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11639         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11640         reverseBoard[r][f] = piece;
11641     }
11642     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11643     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11644     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11645                  || (boards[currentMove][CASTLING][2] == NoRights || 
11646                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11647                  && (boards[currentMove][CASTLING][5] == NoRights || 
11648                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11649       ) {
11650         flipSearch = TRUE;
11651         CopyBoard(flipBoard, soughtBoard);
11652         CopyBoard(rotateBoard, reverseBoard);
11653         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11654             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11655             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11656         }
11657     }
11658     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11659     if(appData.searchMode >= 5) {
11660         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11661         MakePieceList(soughtBoard, minSought);
11662         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11663     }
11664     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11665         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11666 }
11667
11668 GameInfo dummyInfo;
11669
11670 int
11671 GameContainsPosition (FILE *f, ListGame *lg)
11672 {
11673     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11674     int fromX, fromY, toX, toY;
11675     char promoChar;
11676     static int initDone=FALSE;
11677
11678     // weed out games based on numerical tag comparison
11679     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11680     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11681     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11682     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11683     if(!initDone) {
11684         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11685         initDone = TRUE;
11686     }
11687     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11688     else CopyBoard(boards[scratch], initialPosition); // default start position
11689     if(lg->moves) {
11690         turn = btm + 1;
11691         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11692         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11693     }
11694     if(btm) plyNr++;
11695     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11696     fseek(f, lg->offset, 0);
11697     yynewfile(f);
11698     while(1) {
11699         yyboardindex = scratch;
11700         quickFlag = plyNr+1;
11701         next = Myylex();
11702         quickFlag = 0;
11703         switch(next) {
11704             case PGNTag:
11705                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11706             default:
11707                 continue;
11708
11709             case XBoardGame:
11710             case GNUChessGame:
11711                 if(plyNr) return -1; // after we have seen moves, this is for new game
11712               continue;
11713
11714             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11715             case ImpossibleMove:
11716             case WhiteWins: // game ends here with these four
11717             case BlackWins:
11718             case GameIsDrawn:
11719             case GameUnfinished:
11720                 return -1;
11721
11722             case IllegalMove:
11723                 if(appData.testLegality) return -1;
11724             case WhiteCapturesEnPassant:
11725             case BlackCapturesEnPassant:
11726             case WhitePromotion:
11727             case BlackPromotion:
11728             case WhiteNonPromotion:
11729             case BlackNonPromotion:
11730             case NormalMove:
11731             case WhiteKingSideCastle:
11732             case WhiteQueenSideCastle:
11733             case BlackKingSideCastle:
11734             case BlackQueenSideCastle:
11735             case WhiteKingSideCastleWild:
11736             case WhiteQueenSideCastleWild:
11737             case BlackKingSideCastleWild:
11738             case BlackQueenSideCastleWild:
11739             case WhiteHSideCastleFR:
11740             case WhiteASideCastleFR:
11741             case BlackHSideCastleFR:
11742             case BlackASideCastleFR:
11743                 fromX = currentMoveString[0] - AAA;
11744                 fromY = currentMoveString[1] - ONE;
11745                 toX = currentMoveString[2] - AAA;
11746                 toY = currentMoveString[3] - ONE;
11747                 promoChar = currentMoveString[4];
11748                 break;
11749             case WhiteDrop:
11750             case BlackDrop:
11751                 fromX = next == WhiteDrop ?
11752                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11753                   (int) CharToPiece(ToLower(currentMoveString[0]));
11754                 fromY = DROP_RANK;
11755                 toX = currentMoveString[2] - AAA;
11756                 toY = currentMoveString[3] - ONE;
11757                 promoChar = 0;
11758                 break;
11759         }
11760         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11761         plyNr++;
11762         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11763         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11764         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11765         if(appData.findMirror) {
11766             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11767             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11768         }
11769     }
11770 }
11771
11772 /* Load the nth game from open file f */
11773 int
11774 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11775 {
11776     ChessMove cm;
11777     char buf[MSG_SIZ];
11778     int gn = gameNumber;
11779     ListGame *lg = NULL;
11780     int numPGNTags = 0;
11781     int err, pos = -1;
11782     GameMode oldGameMode;
11783     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11784
11785     if (appData.debugMode)
11786         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11787
11788     if (gameMode == Training )
11789         SetTrainingModeOff();
11790
11791     oldGameMode = gameMode;
11792     if (gameMode != BeginningOfGame) {
11793       Reset(FALSE, TRUE);
11794     }
11795
11796     gameFileFP = f;
11797     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11798         fclose(lastLoadGameFP);
11799     }
11800
11801     if (useList) {
11802         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11803
11804         if (lg) {
11805             fseek(f, lg->offset, 0);
11806             GameListHighlight(gameNumber);
11807             pos = lg->position;
11808             gn = 1;
11809         }
11810         else {
11811             DisplayError(_("Game number out of range"), 0);
11812             return FALSE;
11813         }
11814     } else {
11815         GameListDestroy();
11816         if (fseek(f, 0, 0) == -1) {
11817             if (f == lastLoadGameFP ?
11818                 gameNumber == lastLoadGameNumber + 1 :
11819                 gameNumber == 1) {
11820                 gn = 1;
11821             } else {
11822                 DisplayError(_("Can't seek on game file"), 0);
11823                 return FALSE;
11824             }
11825         }
11826     }
11827     lastLoadGameFP = f;
11828     lastLoadGameNumber = gameNumber;
11829     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11830     lastLoadGameUseList = useList;
11831
11832     yynewfile(f);
11833
11834     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11835       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11836                 lg->gameInfo.black);
11837             DisplayTitle(buf);
11838     } else if (*title != NULLCHAR) {
11839         if (gameNumber > 1) {
11840           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11841             DisplayTitle(buf);
11842         } else {
11843             DisplayTitle(title);
11844         }
11845     }
11846
11847     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11848         gameMode = PlayFromGameFile;
11849         ModeHighlight();
11850     }
11851
11852     currentMove = forwardMostMove = backwardMostMove = 0;
11853     CopyBoard(boards[0], initialPosition);
11854     StopClocks();
11855
11856     /*
11857      * Skip the first gn-1 games in the file.
11858      * Also skip over anything that precedes an identifiable
11859      * start of game marker, to avoid being confused by
11860      * garbage at the start of the file.  Currently
11861      * recognized start of game markers are the move number "1",
11862      * the pattern "gnuchess .* game", the pattern
11863      * "^[#;%] [^ ]* game file", and a PGN tag block.
11864      * A game that starts with one of the latter two patterns
11865      * will also have a move number 1, possibly
11866      * following a position diagram.
11867      * 5-4-02: Let's try being more lenient and allowing a game to
11868      * start with an unnumbered move.  Does that break anything?
11869      */
11870     cm = lastLoadGameStart = EndOfFile;
11871     while (gn > 0) {
11872         yyboardindex = forwardMostMove;
11873         cm = (ChessMove) Myylex();
11874         switch (cm) {
11875           case EndOfFile:
11876             if (cmailMsgLoaded) {
11877                 nCmailGames = CMAIL_MAX_GAMES - gn;
11878             } else {
11879                 Reset(TRUE, TRUE);
11880                 DisplayError(_("Game not found in file"), 0);
11881             }
11882             return FALSE;
11883
11884           case GNUChessGame:
11885           case XBoardGame:
11886             gn--;
11887             lastLoadGameStart = cm;
11888             break;
11889
11890           case MoveNumberOne:
11891             switch (lastLoadGameStart) {
11892               case GNUChessGame:
11893               case XBoardGame:
11894               case PGNTag:
11895                 break;
11896               case MoveNumberOne:
11897               case EndOfFile:
11898                 gn--;           /* count this game */
11899                 lastLoadGameStart = cm;
11900                 break;
11901               default:
11902                 /* impossible */
11903                 break;
11904             }
11905             break;
11906
11907           case PGNTag:
11908             switch (lastLoadGameStart) {
11909               case GNUChessGame:
11910               case PGNTag:
11911               case MoveNumberOne:
11912               case EndOfFile:
11913                 gn--;           /* count this game */
11914                 lastLoadGameStart = cm;
11915                 break;
11916               case XBoardGame:
11917                 lastLoadGameStart = cm; /* game counted already */
11918                 break;
11919               default:
11920                 /* impossible */
11921                 break;
11922             }
11923             if (gn > 0) {
11924                 do {
11925                     yyboardindex = forwardMostMove;
11926                     cm = (ChessMove) Myylex();
11927                 } while (cm == PGNTag || cm == Comment);
11928             }
11929             break;
11930
11931           case WhiteWins:
11932           case BlackWins:
11933           case GameIsDrawn:
11934             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11935                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11936                     != CMAIL_OLD_RESULT) {
11937                     nCmailResults ++ ;
11938                     cmailResult[  CMAIL_MAX_GAMES
11939                                 - gn - 1] = CMAIL_OLD_RESULT;
11940                 }
11941             }
11942             break;
11943
11944           case NormalMove:
11945             /* Only a NormalMove can be at the start of a game
11946              * without a position diagram. */
11947             if (lastLoadGameStart == EndOfFile ) {
11948               gn--;
11949               lastLoadGameStart = MoveNumberOne;
11950             }
11951             break;
11952
11953           default:
11954             break;
11955         }
11956     }
11957
11958     if (appData.debugMode)
11959       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11960
11961     if (cm == XBoardGame) {
11962         /* Skip any header junk before position diagram and/or move 1 */
11963         for (;;) {
11964             yyboardindex = forwardMostMove;
11965             cm = (ChessMove) Myylex();
11966
11967             if (cm == EndOfFile ||
11968                 cm == GNUChessGame || cm == XBoardGame) {
11969                 /* Empty game; pretend end-of-file and handle later */
11970                 cm = EndOfFile;
11971                 break;
11972             }
11973
11974             if (cm == MoveNumberOne || cm == PositionDiagram ||
11975                 cm == PGNTag || cm == Comment)
11976               break;
11977         }
11978     } else if (cm == GNUChessGame) {
11979         if (gameInfo.event != NULL) {
11980             free(gameInfo.event);
11981         }
11982         gameInfo.event = StrSave(yy_text);
11983     }
11984
11985     startedFromSetupPosition = FALSE;
11986     while (cm == PGNTag) {
11987         if (appData.debugMode)
11988           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11989         err = ParsePGNTag(yy_text, &gameInfo);
11990         if (!err) numPGNTags++;
11991
11992         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11993         if(gameInfo.variant != oldVariant) {
11994             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11995             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11996             InitPosition(TRUE);
11997             oldVariant = gameInfo.variant;
11998             if (appData.debugMode)
11999               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12000         }
12001
12002
12003         if (gameInfo.fen != NULL) {
12004           Board initial_position;
12005           startedFromSetupPosition = TRUE;
12006           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12007             Reset(TRUE, TRUE);
12008             DisplayError(_("Bad FEN position in file"), 0);
12009             return FALSE;
12010           }
12011           CopyBoard(boards[0], initial_position);
12012           if (blackPlaysFirst) {
12013             currentMove = forwardMostMove = backwardMostMove = 1;
12014             CopyBoard(boards[1], initial_position);
12015             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12016             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12017             timeRemaining[0][1] = whiteTimeRemaining;
12018             timeRemaining[1][1] = blackTimeRemaining;
12019             if (commentList[0] != NULL) {
12020               commentList[1] = commentList[0];
12021               commentList[0] = NULL;
12022             }
12023           } else {
12024             currentMove = forwardMostMove = backwardMostMove = 0;
12025           }
12026           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12027           {   int i;
12028               initialRulePlies = FENrulePlies;
12029               for( i=0; i< nrCastlingRights; i++ )
12030                   initialRights[i] = initial_position[CASTLING][i];
12031           }
12032           yyboardindex = forwardMostMove;
12033           free(gameInfo.fen);
12034           gameInfo.fen = NULL;
12035         }
12036
12037         yyboardindex = forwardMostMove;
12038         cm = (ChessMove) Myylex();
12039
12040         /* Handle comments interspersed among the tags */
12041         while (cm == Comment) {
12042             char *p;
12043             if (appData.debugMode)
12044               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12045             p = yy_text;
12046             AppendComment(currentMove, p, FALSE);
12047             yyboardindex = forwardMostMove;
12048             cm = (ChessMove) Myylex();
12049         }
12050     }
12051
12052     /* don't rely on existence of Event tag since if game was
12053      * pasted from clipboard the Event tag may not exist
12054      */
12055     if (numPGNTags > 0){
12056         char *tags;
12057         if (gameInfo.variant == VariantNormal) {
12058           VariantClass v = StringToVariant(gameInfo.event);
12059           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12060           if(v < VariantShogi) gameInfo.variant = v;
12061         }
12062         if (!matchMode) {
12063           if( appData.autoDisplayTags ) {
12064             tags = PGNTags(&gameInfo);
12065             TagsPopUp(tags, CmailMsg());
12066             free(tags);
12067           }
12068         }
12069     } else {
12070         /* Make something up, but don't display it now */
12071         SetGameInfo();
12072         TagsPopDown();
12073     }
12074
12075     if (cm == PositionDiagram) {
12076         int i, j;
12077         char *p;
12078         Board initial_position;
12079
12080         if (appData.debugMode)
12081           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12082
12083         if (!startedFromSetupPosition) {
12084             p = yy_text;
12085             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12086               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12087                 switch (*p) {
12088                   case '{':
12089                   case '[':
12090                   case '-':
12091                   case ' ':
12092                   case '\t':
12093                   case '\n':
12094                   case '\r':
12095                     break;
12096                   default:
12097                     initial_position[i][j++] = CharToPiece(*p);
12098                     break;
12099                 }
12100             while (*p == ' ' || *p == '\t' ||
12101                    *p == '\n' || *p == '\r') p++;
12102
12103             if (strncmp(p, "black", strlen("black"))==0)
12104               blackPlaysFirst = TRUE;
12105             else
12106               blackPlaysFirst = FALSE;
12107             startedFromSetupPosition = TRUE;
12108
12109             CopyBoard(boards[0], initial_position);
12110             if (blackPlaysFirst) {
12111                 currentMove = forwardMostMove = backwardMostMove = 1;
12112                 CopyBoard(boards[1], initial_position);
12113                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12114                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12115                 timeRemaining[0][1] = whiteTimeRemaining;
12116                 timeRemaining[1][1] = blackTimeRemaining;
12117                 if (commentList[0] != NULL) {
12118                     commentList[1] = commentList[0];
12119                     commentList[0] = NULL;
12120                 }
12121             } else {
12122                 currentMove = forwardMostMove = backwardMostMove = 0;
12123             }
12124         }
12125         yyboardindex = forwardMostMove;
12126         cm = (ChessMove) Myylex();
12127     }
12128
12129     if (first.pr == NoProc) {
12130         StartChessProgram(&first);
12131     }
12132     InitChessProgram(&first, FALSE);
12133     SendToProgram("force\n", &first);
12134     if (startedFromSetupPosition) {
12135         SendBoard(&first, forwardMostMove);
12136     if (appData.debugMode) {
12137         fprintf(debugFP, "Load Game\n");
12138     }
12139         DisplayBothClocks();
12140     }
12141
12142     /* [HGM] server: flag to write setup moves in broadcast file as one */
12143     loadFlag = appData.suppressLoadMoves;
12144
12145     while (cm == Comment) {
12146         char *p;
12147         if (appData.debugMode)
12148           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12149         p = yy_text;
12150         AppendComment(currentMove, p, FALSE);
12151         yyboardindex = forwardMostMove;
12152         cm = (ChessMove) Myylex();
12153     }
12154
12155     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12156         cm == WhiteWins || cm == BlackWins ||
12157         cm == GameIsDrawn || cm == GameUnfinished) {
12158         DisplayMessage("", _("No moves in game"));
12159         if (cmailMsgLoaded) {
12160             if (appData.debugMode)
12161               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12162             ClearHighlights();
12163             flipView = FALSE;
12164         }
12165         DrawPosition(FALSE, boards[currentMove]);
12166         DisplayBothClocks();
12167         gameMode = EditGame;
12168         ModeHighlight();
12169         gameFileFP = NULL;
12170         cmailOldMove = 0;
12171         return TRUE;
12172     }
12173
12174     // [HGM] PV info: routine tests if comment empty
12175     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12176         DisplayComment(currentMove - 1, commentList[currentMove]);
12177     }
12178     if (!matchMode && appData.timeDelay != 0)
12179       DrawPosition(FALSE, boards[currentMove]);
12180
12181     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12182       programStats.ok_to_send = 1;
12183     }
12184
12185     /* if the first token after the PGN tags is a move
12186      * and not move number 1, retrieve it from the parser
12187      */
12188     if (cm != MoveNumberOne)
12189         LoadGameOneMove(cm);
12190
12191     /* load the remaining moves from the file */
12192     while (LoadGameOneMove(EndOfFile)) {
12193       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12194       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12195     }
12196
12197     /* rewind to the start of the game */
12198     currentMove = backwardMostMove;
12199
12200     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12201
12202     if (oldGameMode == AnalyzeFile ||
12203         oldGameMode == AnalyzeMode) {
12204       AnalyzeFileEvent();
12205     }
12206
12207     if (!matchMode && pos > 0) {
12208         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12209     } else
12210     if (matchMode || appData.timeDelay == 0) {
12211       ToEndEvent();
12212     } else if (appData.timeDelay > 0) {
12213       AutoPlayGameLoop();
12214     }
12215
12216     if (appData.debugMode)
12217         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12218
12219     loadFlag = 0; /* [HGM] true game starts */
12220     return TRUE;
12221 }
12222
12223 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12224 int
12225 ReloadPosition (int offset)
12226 {
12227     int positionNumber = lastLoadPositionNumber + offset;
12228     if (lastLoadPositionFP == NULL) {
12229         DisplayError(_("No position has been loaded yet"), 0);
12230         return FALSE;
12231     }
12232     if (positionNumber <= 0) {
12233         DisplayError(_("Can't back up any further"), 0);
12234         return FALSE;
12235     }
12236     return LoadPosition(lastLoadPositionFP, positionNumber,
12237                         lastLoadPositionTitle);
12238 }
12239
12240 /* Load the nth position from the given file */
12241 int
12242 LoadPositionFromFile (char *filename, int n, char *title)
12243 {
12244     FILE *f;
12245     char buf[MSG_SIZ];
12246
12247     if (strcmp(filename, "-") == 0) {
12248         return LoadPosition(stdin, n, "stdin");
12249     } else {
12250         f = fopen(filename, "rb");
12251         if (f == NULL) {
12252             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12253             DisplayError(buf, errno);
12254             return FALSE;
12255         } else {
12256             return LoadPosition(f, n, title);
12257         }
12258     }
12259 }
12260
12261 /* Load the nth position from the given open file, and close it */
12262 int
12263 LoadPosition (FILE *f, int positionNumber, char *title)
12264 {
12265     char *p, line[MSG_SIZ];
12266     Board initial_position;
12267     int i, j, fenMode, pn;
12268
12269     if (gameMode == Training )
12270         SetTrainingModeOff();
12271
12272     if (gameMode != BeginningOfGame) {
12273         Reset(FALSE, TRUE);
12274     }
12275     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12276         fclose(lastLoadPositionFP);
12277     }
12278     if (positionNumber == 0) positionNumber = 1;
12279     lastLoadPositionFP = f;
12280     lastLoadPositionNumber = positionNumber;
12281     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12282     if (first.pr == NoProc && !appData.noChessProgram) {
12283       StartChessProgram(&first);
12284       InitChessProgram(&first, FALSE);
12285     }
12286     pn = positionNumber;
12287     if (positionNumber < 0) {
12288         /* Negative position number means to seek to that byte offset */
12289         if (fseek(f, -positionNumber, 0) == -1) {
12290             DisplayError(_("Can't seek on position file"), 0);
12291             return FALSE;
12292         };
12293         pn = 1;
12294     } else {
12295         if (fseek(f, 0, 0) == -1) {
12296             if (f == lastLoadPositionFP ?
12297                 positionNumber == lastLoadPositionNumber + 1 :
12298                 positionNumber == 1) {
12299                 pn = 1;
12300             } else {
12301                 DisplayError(_("Can't seek on position file"), 0);
12302                 return FALSE;
12303             }
12304         }
12305     }
12306     /* See if this file is FEN or old-style xboard */
12307     if (fgets(line, MSG_SIZ, f) == NULL) {
12308         DisplayError(_("Position not found in file"), 0);
12309         return FALSE;
12310     }
12311     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12312     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12313
12314     if (pn >= 2) {
12315         if (fenMode || line[0] == '#') pn--;
12316         while (pn > 0) {
12317             /* skip positions before number pn */
12318             if (fgets(line, MSG_SIZ, f) == NULL) {
12319                 Reset(TRUE, TRUE);
12320                 DisplayError(_("Position not found in file"), 0);
12321                 return FALSE;
12322             }
12323             if (fenMode || line[0] == '#') pn--;
12324         }
12325     }
12326
12327     if (fenMode) {
12328         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12329             DisplayError(_("Bad FEN position in file"), 0);
12330             return FALSE;
12331         }
12332     } else {
12333         (void) fgets(line, MSG_SIZ, f);
12334         (void) fgets(line, MSG_SIZ, f);
12335
12336         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12337             (void) fgets(line, MSG_SIZ, f);
12338             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12339                 if (*p == ' ')
12340                   continue;
12341                 initial_position[i][j++] = CharToPiece(*p);
12342             }
12343         }
12344
12345         blackPlaysFirst = FALSE;
12346         if (!feof(f)) {
12347             (void) fgets(line, MSG_SIZ, f);
12348             if (strncmp(line, "black", strlen("black"))==0)
12349               blackPlaysFirst = TRUE;
12350         }
12351     }
12352     startedFromSetupPosition = TRUE;
12353
12354     CopyBoard(boards[0], initial_position);
12355     if (blackPlaysFirst) {
12356         currentMove = forwardMostMove = backwardMostMove = 1;
12357         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12358         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12359         CopyBoard(boards[1], initial_position);
12360         DisplayMessage("", _("Black to play"));
12361     } else {
12362         currentMove = forwardMostMove = backwardMostMove = 0;
12363         DisplayMessage("", _("White to play"));
12364     }
12365     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12366     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12367         SendToProgram("force\n", &first);
12368         SendBoard(&first, forwardMostMove);
12369     }
12370     if (appData.debugMode) {
12371 int i, j;
12372   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12373   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12374         fprintf(debugFP, "Load Position\n");
12375     }
12376
12377     if (positionNumber > 1) {
12378       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12379         DisplayTitle(line);
12380     } else {
12381         DisplayTitle(title);
12382     }
12383     gameMode = EditGame;
12384     ModeHighlight();
12385     ResetClocks();
12386     timeRemaining[0][1] = whiteTimeRemaining;
12387     timeRemaining[1][1] = blackTimeRemaining;
12388     DrawPosition(FALSE, boards[currentMove]);
12389
12390     return TRUE;
12391 }
12392
12393
12394 void
12395 CopyPlayerNameIntoFileName (char **dest, char *src)
12396 {
12397     while (*src != NULLCHAR && *src != ',') {
12398         if (*src == ' ') {
12399             *(*dest)++ = '_';
12400             src++;
12401         } else {
12402             *(*dest)++ = *src++;
12403         }
12404     }
12405 }
12406
12407 char *
12408 DefaultFileName (char *ext)
12409 {
12410     static char def[MSG_SIZ];
12411     char *p;
12412
12413     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12414         p = def;
12415         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12416         *p++ = '-';
12417         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12418         *p++ = '.';
12419         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12420     } else {
12421         def[0] = NULLCHAR;
12422     }
12423     return def;
12424 }
12425
12426 /* Save the current game to the given file */
12427 int
12428 SaveGameToFile (char *filename, int append)
12429 {
12430     FILE *f;
12431     char buf[MSG_SIZ];
12432     int result, i, t,tot=0;
12433
12434     if (strcmp(filename, "-") == 0) {
12435         return SaveGame(stdout, 0, NULL);
12436     } else {
12437         for(i=0; i<10; i++) { // upto 10 tries
12438              f = fopen(filename, append ? "a" : "w");
12439              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12440              if(f || errno != 13) break;
12441              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12442              tot += t;
12443         }
12444         if (f == NULL) {
12445             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12446             DisplayError(buf, errno);
12447             return FALSE;
12448         } else {
12449             safeStrCpy(buf, lastMsg, MSG_SIZ);
12450             DisplayMessage(_("Waiting for access to save file"), "");
12451             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12452             DisplayMessage(_("Saving game"), "");
12453             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12454             result = SaveGame(f, 0, NULL);
12455             DisplayMessage(buf, "");
12456             return result;
12457         }
12458     }
12459 }
12460
12461 char *
12462 SavePart (char *str)
12463 {
12464     static char buf[MSG_SIZ];
12465     char *p;
12466
12467     p = strchr(str, ' ');
12468     if (p == NULL) return str;
12469     strncpy(buf, str, p - str);
12470     buf[p - str] = NULLCHAR;
12471     return buf;
12472 }
12473
12474 #define PGN_MAX_LINE 75
12475
12476 #define PGN_SIDE_WHITE  0
12477 #define PGN_SIDE_BLACK  1
12478
12479 static int
12480 FindFirstMoveOutOfBook (int side)
12481 {
12482     int result = -1;
12483
12484     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12485         int index = backwardMostMove;
12486         int has_book_hit = 0;
12487
12488         if( (index % 2) != side ) {
12489             index++;
12490         }
12491
12492         while( index < forwardMostMove ) {
12493             /* Check to see if engine is in book */
12494             int depth = pvInfoList[index].depth;
12495             int score = pvInfoList[index].score;
12496             int in_book = 0;
12497
12498             if( depth <= 2 ) {
12499                 in_book = 1;
12500             }
12501             else if( score == 0 && depth == 63 ) {
12502                 in_book = 1; /* Zappa */
12503             }
12504             else if( score == 2 && depth == 99 ) {
12505                 in_book = 1; /* Abrok */
12506             }
12507
12508             has_book_hit += in_book;
12509
12510             if( ! in_book ) {
12511                 result = index;
12512
12513                 break;
12514             }
12515
12516             index += 2;
12517         }
12518     }
12519
12520     return result;
12521 }
12522
12523 void
12524 GetOutOfBookInfo (char * buf)
12525 {
12526     int oob[2];
12527     int i;
12528     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12529
12530     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12531     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12532
12533     *buf = '\0';
12534
12535     if( oob[0] >= 0 || oob[1] >= 0 ) {
12536         for( i=0; i<2; i++ ) {
12537             int idx = oob[i];
12538
12539             if( idx >= 0 ) {
12540                 if( i > 0 && oob[0] >= 0 ) {
12541                     strcat( buf, "   " );
12542                 }
12543
12544                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12545                 sprintf( buf+strlen(buf), "%s%.2f",
12546                     pvInfoList[idx].score >= 0 ? "+" : "",
12547                     pvInfoList[idx].score / 100.0 );
12548             }
12549         }
12550     }
12551 }
12552
12553 /* Save game in PGN style and close the file */
12554 int
12555 SaveGamePGN (FILE *f)
12556 {
12557     int i, offset, linelen, newblock;
12558 //    char *movetext;
12559     char numtext[32];
12560     int movelen, numlen, blank;
12561     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12562
12563     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12564
12565     PrintPGNTags(f, &gameInfo);
12566
12567     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12568
12569     if (backwardMostMove > 0 || startedFromSetupPosition) {
12570         char *fen = PositionToFEN(backwardMostMove, NULL);
12571         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12572         fprintf(f, "\n{--------------\n");
12573         PrintPosition(f, backwardMostMove);
12574         fprintf(f, "--------------}\n");
12575         free(fen);
12576     }
12577     else {
12578         /* [AS] Out of book annotation */
12579         if( appData.saveOutOfBookInfo ) {
12580             char buf[64];
12581
12582             GetOutOfBookInfo( buf );
12583
12584             if( buf[0] != '\0' ) {
12585                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12586             }
12587         }
12588
12589         fprintf(f, "\n");
12590     }
12591
12592     i = backwardMostMove;
12593     linelen = 0;
12594     newblock = TRUE;
12595
12596     while (i < forwardMostMove) {
12597         /* Print comments preceding this move */
12598         if (commentList[i] != NULL) {
12599             if (linelen > 0) fprintf(f, "\n");
12600             fprintf(f, "%s", commentList[i]);
12601             linelen = 0;
12602             newblock = TRUE;
12603         }
12604
12605         /* Format move number */
12606         if ((i % 2) == 0)
12607           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12608         else
12609           if (newblock)
12610             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12611           else
12612             numtext[0] = NULLCHAR;
12613
12614         numlen = strlen(numtext);
12615         newblock = FALSE;
12616
12617         /* Print move number */
12618         blank = linelen > 0 && numlen > 0;
12619         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12620             fprintf(f, "\n");
12621             linelen = 0;
12622             blank = 0;
12623         }
12624         if (blank) {
12625             fprintf(f, " ");
12626             linelen++;
12627         }
12628         fprintf(f, "%s", numtext);
12629         linelen += numlen;
12630
12631         /* Get move */
12632         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12633         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12634
12635         /* Print move */
12636         blank = linelen > 0 && movelen > 0;
12637         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12638             fprintf(f, "\n");
12639             linelen = 0;
12640             blank = 0;
12641         }
12642         if (blank) {
12643             fprintf(f, " ");
12644             linelen++;
12645         }
12646         fprintf(f, "%s", move_buffer);
12647         linelen += movelen;
12648
12649         /* [AS] Add PV info if present */
12650         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12651             /* [HGM] add time */
12652             char buf[MSG_SIZ]; int seconds;
12653
12654             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12655
12656             if( seconds <= 0)
12657               buf[0] = 0;
12658             else
12659               if( seconds < 30 )
12660                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12661               else
12662                 {
12663                   seconds = (seconds + 4)/10; // round to full seconds
12664                   if( seconds < 60 )
12665                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12666                   else
12667                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12668                 }
12669
12670             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12671                       pvInfoList[i].score >= 0 ? "+" : "",
12672                       pvInfoList[i].score / 100.0,
12673                       pvInfoList[i].depth,
12674                       buf );
12675
12676             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12677
12678             /* Print score/depth */
12679             blank = linelen > 0 && movelen > 0;
12680             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12681                 fprintf(f, "\n");
12682                 linelen = 0;
12683                 blank = 0;
12684             }
12685             if (blank) {
12686                 fprintf(f, " ");
12687                 linelen++;
12688             }
12689             fprintf(f, "%s", move_buffer);
12690             linelen += movelen;
12691         }
12692
12693         i++;
12694     }
12695
12696     /* Start a new line */
12697     if (linelen > 0) fprintf(f, "\n");
12698
12699     /* Print comments after last move */
12700     if (commentList[i] != NULL) {
12701         fprintf(f, "%s\n", commentList[i]);
12702     }
12703
12704     /* Print result */
12705     if (gameInfo.resultDetails != NULL &&
12706         gameInfo.resultDetails[0] != NULLCHAR) {
12707         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12708                 PGNResult(gameInfo.result));
12709     } else {
12710         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12711     }
12712
12713     fclose(f);
12714     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12715     return TRUE;
12716 }
12717
12718 /* Save game in old style and close the file */
12719 int
12720 SaveGameOldStyle (FILE *f)
12721 {
12722     int i, offset;
12723     time_t tm;
12724
12725     tm = time((time_t *) NULL);
12726
12727     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12728     PrintOpponents(f);
12729
12730     if (backwardMostMove > 0 || startedFromSetupPosition) {
12731         fprintf(f, "\n[--------------\n");
12732         PrintPosition(f, backwardMostMove);
12733         fprintf(f, "--------------]\n");
12734     } else {
12735         fprintf(f, "\n");
12736     }
12737
12738     i = backwardMostMove;
12739     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12740
12741     while (i < forwardMostMove) {
12742         if (commentList[i] != NULL) {
12743             fprintf(f, "[%s]\n", commentList[i]);
12744         }
12745
12746         if ((i % 2) == 1) {
12747             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12748             i++;
12749         } else {
12750             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12751             i++;
12752             if (commentList[i] != NULL) {
12753                 fprintf(f, "\n");
12754                 continue;
12755             }
12756             if (i >= forwardMostMove) {
12757                 fprintf(f, "\n");
12758                 break;
12759             }
12760             fprintf(f, "%s\n", parseList[i]);
12761             i++;
12762         }
12763     }
12764
12765     if (commentList[i] != NULL) {
12766         fprintf(f, "[%s]\n", commentList[i]);
12767     }
12768
12769     /* This isn't really the old style, but it's close enough */
12770     if (gameInfo.resultDetails != NULL &&
12771         gameInfo.resultDetails[0] != NULLCHAR) {
12772         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12773                 gameInfo.resultDetails);
12774     } else {
12775         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12776     }
12777
12778     fclose(f);
12779     return TRUE;
12780 }
12781
12782 /* Save the current game to open file f and close the file */
12783 int
12784 SaveGame (FILE *f, int dummy, char *dummy2)
12785 {
12786     if (gameMode == EditPosition) EditPositionDone(TRUE);
12787     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12788     if (appData.oldSaveStyle)
12789       return SaveGameOldStyle(f);
12790     else
12791       return SaveGamePGN(f);
12792 }
12793
12794 /* Save the current position to the given file */
12795 int
12796 SavePositionToFile (char *filename)
12797 {
12798     FILE *f;
12799     char buf[MSG_SIZ];
12800
12801     if (strcmp(filename, "-") == 0) {
12802         return SavePosition(stdout, 0, NULL);
12803     } else {
12804         f = fopen(filename, "a");
12805         if (f == NULL) {
12806             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12807             DisplayError(buf, errno);
12808             return FALSE;
12809         } else {
12810             safeStrCpy(buf, lastMsg, MSG_SIZ);
12811             DisplayMessage(_("Waiting for access to save file"), "");
12812             flock(fileno(f), LOCK_EX); // [HGM] lock
12813             DisplayMessage(_("Saving position"), "");
12814             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12815             SavePosition(f, 0, NULL);
12816             DisplayMessage(buf, "");
12817             return TRUE;
12818         }
12819     }
12820 }
12821
12822 /* Save the current position to the given open file and close the file */
12823 int
12824 SavePosition (FILE *f, int dummy, char *dummy2)
12825 {
12826     time_t tm;
12827     char *fen;
12828
12829     if (gameMode == EditPosition) EditPositionDone(TRUE);
12830     if (appData.oldSaveStyle) {
12831         tm = time((time_t *) NULL);
12832
12833         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12834         PrintOpponents(f);
12835         fprintf(f, "[--------------\n");
12836         PrintPosition(f, currentMove);
12837         fprintf(f, "--------------]\n");
12838     } else {
12839         fen = PositionToFEN(currentMove, NULL);
12840         fprintf(f, "%s\n", fen);
12841         free(fen);
12842     }
12843     fclose(f);
12844     return TRUE;
12845 }
12846
12847 void
12848 ReloadCmailMsgEvent (int unregister)
12849 {
12850 #if !WIN32
12851     static char *inFilename = NULL;
12852     static char *outFilename;
12853     int i;
12854     struct stat inbuf, outbuf;
12855     int status;
12856
12857     /* Any registered moves are unregistered if unregister is set, */
12858     /* i.e. invoked by the signal handler */
12859     if (unregister) {
12860         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12861             cmailMoveRegistered[i] = FALSE;
12862             if (cmailCommentList[i] != NULL) {
12863                 free(cmailCommentList[i]);
12864                 cmailCommentList[i] = NULL;
12865             }
12866         }
12867         nCmailMovesRegistered = 0;
12868     }
12869
12870     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12871         cmailResult[i] = CMAIL_NOT_RESULT;
12872     }
12873     nCmailResults = 0;
12874
12875     if (inFilename == NULL) {
12876         /* Because the filenames are static they only get malloced once  */
12877         /* and they never get freed                                      */
12878         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12879         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12880
12881         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12882         sprintf(outFilename, "%s.out", appData.cmailGameName);
12883     }
12884
12885     status = stat(outFilename, &outbuf);
12886     if (status < 0) {
12887         cmailMailedMove = FALSE;
12888     } else {
12889         status = stat(inFilename, &inbuf);
12890         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12891     }
12892
12893     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12894        counts the games, notes how each one terminated, etc.
12895
12896        It would be nice to remove this kludge and instead gather all
12897        the information while building the game list.  (And to keep it
12898        in the game list nodes instead of having a bunch of fixed-size
12899        parallel arrays.)  Note this will require getting each game's
12900        termination from the PGN tags, as the game list builder does
12901        not process the game moves.  --mann
12902        */
12903     cmailMsgLoaded = TRUE;
12904     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12905
12906     /* Load first game in the file or popup game menu */
12907     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12908
12909 #endif /* !WIN32 */
12910     return;
12911 }
12912
12913 int
12914 RegisterMove ()
12915 {
12916     FILE *f;
12917     char string[MSG_SIZ];
12918
12919     if (   cmailMailedMove
12920         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12921         return TRUE;            /* Allow free viewing  */
12922     }
12923
12924     /* Unregister move to ensure that we don't leave RegisterMove        */
12925     /* with the move registered when the conditions for registering no   */
12926     /* longer hold                                                       */
12927     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12928         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12929         nCmailMovesRegistered --;
12930
12931         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12932           {
12933               free(cmailCommentList[lastLoadGameNumber - 1]);
12934               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12935           }
12936     }
12937
12938     if (cmailOldMove == -1) {
12939         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12940         return FALSE;
12941     }
12942
12943     if (currentMove > cmailOldMove + 1) {
12944         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12945         return FALSE;
12946     }
12947
12948     if (currentMove < cmailOldMove) {
12949         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12950         return FALSE;
12951     }
12952
12953     if (forwardMostMove > currentMove) {
12954         /* Silently truncate extra moves */
12955         TruncateGame();
12956     }
12957
12958     if (   (currentMove == cmailOldMove + 1)
12959         || (   (currentMove == cmailOldMove)
12960             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12961                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12962         if (gameInfo.result != GameUnfinished) {
12963             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12964         }
12965
12966         if (commentList[currentMove] != NULL) {
12967             cmailCommentList[lastLoadGameNumber - 1]
12968               = StrSave(commentList[currentMove]);
12969         }
12970         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12971
12972         if (appData.debugMode)
12973           fprintf(debugFP, "Saving %s for game %d\n",
12974                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12975
12976         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12977
12978         f = fopen(string, "w");
12979         if (appData.oldSaveStyle) {
12980             SaveGameOldStyle(f); /* also closes the file */
12981
12982             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12983             f = fopen(string, "w");
12984             SavePosition(f, 0, NULL); /* also closes the file */
12985         } else {
12986             fprintf(f, "{--------------\n");
12987             PrintPosition(f, currentMove);
12988             fprintf(f, "--------------}\n\n");
12989
12990             SaveGame(f, 0, NULL); /* also closes the file*/
12991         }
12992
12993         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12994         nCmailMovesRegistered ++;
12995     } else if (nCmailGames == 1) {
12996         DisplayError(_("You have not made a move yet"), 0);
12997         return FALSE;
12998     }
12999
13000     return TRUE;
13001 }
13002
13003 void
13004 MailMoveEvent ()
13005 {
13006 #if !WIN32
13007     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13008     FILE *commandOutput;
13009     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13010     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13011     int nBuffers;
13012     int i;
13013     int archived;
13014     char *arcDir;
13015
13016     if (! cmailMsgLoaded) {
13017         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13018         return;
13019     }
13020
13021     if (nCmailGames == nCmailResults) {
13022         DisplayError(_("No unfinished games"), 0);
13023         return;
13024     }
13025
13026 #if CMAIL_PROHIBIT_REMAIL
13027     if (cmailMailedMove) {
13028       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);
13029         DisplayError(msg, 0);
13030         return;
13031     }
13032 #endif
13033
13034     if (! (cmailMailedMove || RegisterMove())) return;
13035
13036     if (   cmailMailedMove
13037         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13038       snprintf(string, MSG_SIZ, partCommandString,
13039                appData.debugMode ? " -v" : "", appData.cmailGameName);
13040         commandOutput = popen(string, "r");
13041
13042         if (commandOutput == NULL) {
13043             DisplayError(_("Failed to invoke cmail"), 0);
13044         } else {
13045             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13046                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13047             }
13048             if (nBuffers > 1) {
13049                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13050                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13051                 nBytes = MSG_SIZ - 1;
13052             } else {
13053                 (void) memcpy(msg, buffer, nBytes);
13054             }
13055             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13056
13057             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13058                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13059
13060                 archived = TRUE;
13061                 for (i = 0; i < nCmailGames; i ++) {
13062                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13063                         archived = FALSE;
13064                     }
13065                 }
13066                 if (   archived
13067                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13068                         != NULL)) {
13069                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13070                            arcDir,
13071                            appData.cmailGameName,
13072                            gameInfo.date);
13073                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13074                     cmailMsgLoaded = FALSE;
13075                 }
13076             }
13077
13078             DisplayInformation(msg);
13079             pclose(commandOutput);
13080         }
13081     } else {
13082         if ((*cmailMsg) != '\0') {
13083             DisplayInformation(cmailMsg);
13084         }
13085     }
13086
13087     return;
13088 #endif /* !WIN32 */
13089 }
13090
13091 char *
13092 CmailMsg ()
13093 {
13094 #if WIN32
13095     return NULL;
13096 #else
13097     int  prependComma = 0;
13098     char number[5];
13099     char string[MSG_SIZ];       /* Space for game-list */
13100     int  i;
13101
13102     if (!cmailMsgLoaded) return "";
13103
13104     if (cmailMailedMove) {
13105       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13106     } else {
13107         /* Create a list of games left */
13108       snprintf(string, MSG_SIZ, "[");
13109         for (i = 0; i < nCmailGames; i ++) {
13110             if (! (   cmailMoveRegistered[i]
13111                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13112                 if (prependComma) {
13113                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13114                 } else {
13115                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13116                     prependComma = 1;
13117                 }
13118
13119                 strcat(string, number);
13120             }
13121         }
13122         strcat(string, "]");
13123
13124         if (nCmailMovesRegistered + nCmailResults == 0) {
13125             switch (nCmailGames) {
13126               case 1:
13127                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13128                 break;
13129
13130               case 2:
13131                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13132                 break;
13133
13134               default:
13135                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13136                          nCmailGames);
13137                 break;
13138             }
13139         } else {
13140             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13141               case 1:
13142                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13143                          string);
13144                 break;
13145
13146               case 0:
13147                 if (nCmailResults == nCmailGames) {
13148                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13149                 } else {
13150                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13151                 }
13152                 break;
13153
13154               default:
13155                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13156                          string);
13157             }
13158         }
13159     }
13160     return cmailMsg;
13161 #endif /* WIN32 */
13162 }
13163
13164 void
13165 ResetGameEvent ()
13166 {
13167     if (gameMode == Training)
13168       SetTrainingModeOff();
13169
13170     Reset(TRUE, TRUE);
13171     cmailMsgLoaded = FALSE;
13172     if (appData.icsActive) {
13173       SendToICS(ics_prefix);
13174       SendToICS("refresh\n");
13175     }
13176 }
13177
13178 void
13179 ExitEvent (int status)
13180 {
13181     exiting++;
13182     if (exiting > 2) {
13183       /* Give up on clean exit */
13184       exit(status);
13185     }
13186     if (exiting > 1) {
13187       /* Keep trying for clean exit */
13188       return;
13189     }
13190
13191     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13192
13193     if (telnetISR != NULL) {
13194       RemoveInputSource(telnetISR);
13195     }
13196     if (icsPR != NoProc) {
13197       DestroyChildProcess(icsPR, TRUE);
13198     }
13199
13200     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13201     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13202
13203     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13204     /* make sure this other one finishes before killing it!                  */
13205     if(endingGame) { int count = 0;
13206         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13207         while(endingGame && count++ < 10) DoSleep(1);
13208         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13209     }
13210
13211     /* Kill off chess programs */
13212     if (first.pr != NoProc) {
13213         ExitAnalyzeMode();
13214
13215         DoSleep( appData.delayBeforeQuit );
13216         SendToProgram("quit\n", &first);
13217         DoSleep( appData.delayAfterQuit );
13218         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13219     }
13220     if (second.pr != NoProc) {
13221         DoSleep( appData.delayBeforeQuit );
13222         SendToProgram("quit\n", &second);
13223         DoSleep( appData.delayAfterQuit );
13224         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13225     }
13226     if (first.isr != NULL) {
13227         RemoveInputSource(first.isr);
13228     }
13229     if (second.isr != NULL) {
13230         RemoveInputSource(second.isr);
13231     }
13232
13233     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13234     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13235
13236     ShutDownFrontEnd();
13237     exit(status);
13238 }
13239
13240 void
13241 PauseEvent ()
13242 {
13243     if (appData.debugMode)
13244         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13245     if (pausing) {
13246         pausing = FALSE;
13247         ModeHighlight();
13248         if (gameMode == MachinePlaysWhite ||
13249             gameMode == MachinePlaysBlack) {
13250             StartClocks();
13251         } else {
13252             DisplayBothClocks();
13253         }
13254         if (gameMode == PlayFromGameFile) {
13255             if (appData.timeDelay >= 0)
13256                 AutoPlayGameLoop();
13257         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13258             Reset(FALSE, TRUE);
13259             SendToICS(ics_prefix);
13260             SendToICS("refresh\n");
13261         } else if (currentMove < forwardMostMove) {
13262             ForwardInner(forwardMostMove);
13263         }
13264         pauseExamInvalid = FALSE;
13265     } else {
13266         switch (gameMode) {
13267           default:
13268             return;
13269           case IcsExamining:
13270             pauseExamForwardMostMove = forwardMostMove;
13271             pauseExamInvalid = FALSE;
13272             /* fall through */
13273           case IcsObserving:
13274           case IcsPlayingWhite:
13275           case IcsPlayingBlack:
13276             pausing = TRUE;
13277             ModeHighlight();
13278             return;
13279           case PlayFromGameFile:
13280             (void) StopLoadGameTimer();
13281             pausing = TRUE;
13282             ModeHighlight();
13283             break;
13284           case BeginningOfGame:
13285             if (appData.icsActive) return;
13286             /* else fall through */
13287           case MachinePlaysWhite:
13288           case MachinePlaysBlack:
13289           case TwoMachinesPlay:
13290             if (forwardMostMove == 0)
13291               return;           /* don't pause if no one has moved */
13292             if ((gameMode == MachinePlaysWhite &&
13293                  !WhiteOnMove(forwardMostMove)) ||
13294                 (gameMode == MachinePlaysBlack &&
13295                  WhiteOnMove(forwardMostMove))) {
13296                 StopClocks();
13297             }
13298             pausing = TRUE;
13299             ModeHighlight();
13300             break;
13301         }
13302     }
13303 }
13304
13305 void
13306 EditCommentEvent ()
13307 {
13308     char title[MSG_SIZ];
13309
13310     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13311       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13312     } else {
13313       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13314                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13315                parseList[currentMove - 1]);
13316     }
13317
13318     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13319 }
13320
13321
13322 void
13323 EditTagsEvent ()
13324 {
13325     char *tags = PGNTags(&gameInfo);
13326     bookUp = FALSE;
13327     EditTagsPopUp(tags, NULL);
13328     free(tags);
13329 }
13330
13331 void
13332 AnalyzeModeEvent ()
13333 {
13334     if (appData.noChessProgram || gameMode == AnalyzeMode)
13335       return;
13336
13337     if (gameMode != AnalyzeFile) {
13338         if (!appData.icsEngineAnalyze) {
13339                EditGameEvent();
13340                if (gameMode != EditGame) return;
13341         }
13342         ResurrectChessProgram();
13343         SendToProgram("analyze\n", &first);
13344         first.analyzing = TRUE;
13345         /*first.maybeThinking = TRUE;*/
13346         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13347         EngineOutputPopUp();
13348     }
13349     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13350     pausing = FALSE;
13351     ModeHighlight();
13352     SetGameInfo();
13353
13354     StartAnalysisClock();
13355     GetTimeMark(&lastNodeCountTime);
13356     lastNodeCount = 0;
13357 }
13358
13359 void
13360 AnalyzeFileEvent ()
13361 {
13362     if (appData.noChessProgram || gameMode == AnalyzeFile)
13363       return;
13364
13365     if (gameMode != AnalyzeMode) {
13366         EditGameEvent();
13367         if (gameMode != EditGame) return;
13368         ResurrectChessProgram();
13369         SendToProgram("analyze\n", &first);
13370         first.analyzing = TRUE;
13371         /*first.maybeThinking = TRUE;*/
13372         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13373         EngineOutputPopUp();
13374     }
13375     gameMode = AnalyzeFile;
13376     pausing = FALSE;
13377     ModeHighlight();
13378     SetGameInfo();
13379
13380     StartAnalysisClock();
13381     GetTimeMark(&lastNodeCountTime);
13382     lastNodeCount = 0;
13383     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13384 }
13385
13386 void
13387 MachineWhiteEvent ()
13388 {
13389     char buf[MSG_SIZ];
13390     char *bookHit = NULL;
13391
13392     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13393       return;
13394
13395
13396     if (gameMode == PlayFromGameFile ||
13397         gameMode == TwoMachinesPlay  ||
13398         gameMode == Training         ||
13399         gameMode == AnalyzeMode      ||
13400         gameMode == EndOfGame)
13401         EditGameEvent();
13402
13403     if (gameMode == EditPosition)
13404         EditPositionDone(TRUE);
13405
13406     if (!WhiteOnMove(currentMove)) {
13407         DisplayError(_("It is not White's turn"), 0);
13408         return;
13409     }
13410
13411     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13412       ExitAnalyzeMode();
13413
13414     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13415         gameMode == AnalyzeFile)
13416         TruncateGame();
13417
13418     ResurrectChessProgram();    /* in case it isn't running */
13419     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13420         gameMode = MachinePlaysWhite;
13421         ResetClocks();
13422     } else
13423     gameMode = MachinePlaysWhite;
13424     pausing = FALSE;
13425     ModeHighlight();
13426     SetGameInfo();
13427     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13428     DisplayTitle(buf);
13429     if (first.sendName) {
13430       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13431       SendToProgram(buf, &first);
13432     }
13433     if (first.sendTime) {
13434       if (first.useColors) {
13435         SendToProgram("black\n", &first); /*gnu kludge*/
13436       }
13437       SendTimeRemaining(&first, TRUE);
13438     }
13439     if (first.useColors) {
13440       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13441     }
13442     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13443     SetMachineThinkingEnables();
13444     first.maybeThinking = TRUE;
13445     StartClocks();
13446     firstMove = FALSE;
13447
13448     if (appData.autoFlipView && !flipView) {
13449       flipView = !flipView;
13450       DrawPosition(FALSE, NULL);
13451       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13452     }
13453
13454     if(bookHit) { // [HGM] book: simulate book reply
13455         static char bookMove[MSG_SIZ]; // a bit generous?
13456
13457         programStats.nodes = programStats.depth = programStats.time =
13458         programStats.score = programStats.got_only_move = 0;
13459         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13460
13461         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13462         strcat(bookMove, bookHit);
13463         HandleMachineMove(bookMove, &first);
13464     }
13465 }
13466
13467 void
13468 MachineBlackEvent ()
13469 {
13470   char buf[MSG_SIZ];
13471   char *bookHit = NULL;
13472
13473     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13474         return;
13475
13476
13477     if (gameMode == PlayFromGameFile ||
13478         gameMode == TwoMachinesPlay  ||
13479         gameMode == Training         ||
13480         gameMode == AnalyzeMode      ||
13481         gameMode == EndOfGame)
13482         EditGameEvent();
13483
13484     if (gameMode == EditPosition)
13485         EditPositionDone(TRUE);
13486
13487     if (WhiteOnMove(currentMove)) {
13488         DisplayError(_("It is not Black's turn"), 0);
13489         return;
13490     }
13491
13492     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13493       ExitAnalyzeMode();
13494
13495     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13496         gameMode == AnalyzeFile)
13497         TruncateGame();
13498
13499     ResurrectChessProgram();    /* in case it isn't running */
13500     gameMode = MachinePlaysBlack;
13501     pausing = FALSE;
13502     ModeHighlight();
13503     SetGameInfo();
13504     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13505     DisplayTitle(buf);
13506     if (first.sendName) {
13507       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13508       SendToProgram(buf, &first);
13509     }
13510     if (first.sendTime) {
13511       if (first.useColors) {
13512         SendToProgram("white\n", &first); /*gnu kludge*/
13513       }
13514       SendTimeRemaining(&first, FALSE);
13515     }
13516     if (first.useColors) {
13517       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13518     }
13519     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13520     SetMachineThinkingEnables();
13521     first.maybeThinking = TRUE;
13522     StartClocks();
13523
13524     if (appData.autoFlipView && flipView) {
13525       flipView = !flipView;
13526       DrawPosition(FALSE, NULL);
13527       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13528     }
13529     if(bookHit) { // [HGM] book: simulate book reply
13530         static char bookMove[MSG_SIZ]; // a bit generous?
13531
13532         programStats.nodes = programStats.depth = programStats.time =
13533         programStats.score = programStats.got_only_move = 0;
13534         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13535
13536         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13537         strcat(bookMove, bookHit);
13538         HandleMachineMove(bookMove, &first);
13539     }
13540 }
13541
13542
13543 void
13544 DisplayTwoMachinesTitle ()
13545 {
13546     char buf[MSG_SIZ];
13547     if (appData.matchGames > 0) {
13548         if(appData.tourneyFile[0]) {
13549           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13550                    gameInfo.white, _("vs."), gameInfo.black,
13551                    nextGame+1, appData.matchGames+1,
13552                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13553         } else 
13554         if (first.twoMachinesColor[0] == 'w') {
13555           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13556                    gameInfo.white, _("vs."),  gameInfo.black,
13557                    first.matchWins, second.matchWins,
13558                    matchGame - 1 - (first.matchWins + second.matchWins));
13559         } else {
13560           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13561                    gameInfo.white, _("vs."), gameInfo.black,
13562                    second.matchWins, first.matchWins,
13563                    matchGame - 1 - (first.matchWins + second.matchWins));
13564         }
13565     } else {
13566       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13567     }
13568     DisplayTitle(buf);
13569 }
13570
13571 void
13572 SettingsMenuIfReady ()
13573 {
13574   if (second.lastPing != second.lastPong) {
13575     DisplayMessage("", _("Waiting for second chess program"));
13576     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13577     return;
13578   }
13579   ThawUI();
13580   DisplayMessage("", "");
13581   SettingsPopUp(&second);
13582 }
13583
13584 int
13585 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13586 {
13587     char buf[MSG_SIZ];
13588     if (cps->pr == NoProc) {
13589         StartChessProgram(cps);
13590         if (cps->protocolVersion == 1) {
13591           retry();
13592         } else {
13593           /* kludge: allow timeout for initial "feature" command */
13594           FreezeUI();
13595           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13596           DisplayMessage("", buf);
13597           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13598         }
13599         return 1;
13600     }
13601     return 0;
13602 }
13603
13604 void
13605 TwoMachinesEvent P((void))
13606 {
13607     int i;
13608     char buf[MSG_SIZ];
13609     ChessProgramState *onmove;
13610     char *bookHit = NULL;
13611     static int stalling = 0;
13612     TimeMark now;
13613     long wait;
13614
13615     if (appData.noChessProgram) return;
13616
13617     switch (gameMode) {
13618       case TwoMachinesPlay:
13619         return;
13620       case MachinePlaysWhite:
13621       case MachinePlaysBlack:
13622         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13623             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13624             return;
13625         }
13626         /* fall through */
13627       case BeginningOfGame:
13628       case PlayFromGameFile:
13629       case EndOfGame:
13630         EditGameEvent();
13631         if (gameMode != EditGame) return;
13632         break;
13633       case EditPosition:
13634         EditPositionDone(TRUE);
13635         break;
13636       case AnalyzeMode:
13637       case AnalyzeFile:
13638         ExitAnalyzeMode();
13639         break;
13640       case EditGame:
13641       default:
13642         break;
13643     }
13644
13645 //    forwardMostMove = currentMove;
13646     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13647
13648     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13649
13650     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13651     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13652       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13653       return;
13654     }
13655
13656     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13657         DisplayError("second engine does not play this", 0);
13658         return;
13659     }
13660
13661     if(!stalling) {
13662       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13663       SendToProgram("force\n", &second);
13664       stalling = 1;
13665       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13666       return;
13667     }
13668     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13669     if(appData.matchPause>10000 || appData.matchPause<10)
13670                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13671     wait = SubtractTimeMarks(&now, &pauseStart);
13672     if(wait < appData.matchPause) {
13673         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13674         return;
13675     }
13676     // we are now committed to starting the game
13677     stalling = 0;
13678     DisplayMessage("", "");
13679     if (startedFromSetupPosition) {
13680         SendBoard(&second, backwardMostMove);
13681     if (appData.debugMode) {
13682         fprintf(debugFP, "Two Machines\n");
13683     }
13684     }
13685     for (i = backwardMostMove; i < forwardMostMove; i++) {
13686         SendMoveToProgram(i, &second);
13687     }
13688
13689     gameMode = TwoMachinesPlay;
13690     pausing = FALSE;
13691     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13692     SetGameInfo();
13693     DisplayTwoMachinesTitle();
13694     firstMove = TRUE;
13695     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13696         onmove = &first;
13697     } else {
13698         onmove = &second;
13699     }
13700     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13701     SendToProgram(first.computerString, &first);
13702     if (first.sendName) {
13703       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13704       SendToProgram(buf, &first);
13705     }
13706     SendToProgram(second.computerString, &second);
13707     if (second.sendName) {
13708       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13709       SendToProgram(buf, &second);
13710     }
13711
13712     ResetClocks();
13713     if (!first.sendTime || !second.sendTime) {
13714         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13715         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13716     }
13717     if (onmove->sendTime) {
13718       if (onmove->useColors) {
13719         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13720       }
13721       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13722     }
13723     if (onmove->useColors) {
13724       SendToProgram(onmove->twoMachinesColor, onmove);
13725     }
13726     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13727 //    SendToProgram("go\n", onmove);
13728     onmove->maybeThinking = TRUE;
13729     SetMachineThinkingEnables();
13730
13731     StartClocks();
13732
13733     if(bookHit) { // [HGM] book: simulate book reply
13734         static char bookMove[MSG_SIZ]; // a bit generous?
13735
13736         programStats.nodes = programStats.depth = programStats.time =
13737         programStats.score = programStats.got_only_move = 0;
13738         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13739
13740         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13741         strcat(bookMove, bookHit);
13742         savedMessage = bookMove; // args for deferred call
13743         savedState = onmove;
13744         ScheduleDelayedEvent(DeferredBookMove, 1);
13745     }
13746 }
13747
13748 void
13749 TrainingEvent ()
13750 {
13751     if (gameMode == Training) {
13752       SetTrainingModeOff();
13753       gameMode = PlayFromGameFile;
13754       DisplayMessage("", _("Training mode off"));
13755     } else {
13756       gameMode = Training;
13757       animateTraining = appData.animate;
13758
13759       /* make sure we are not already at the end of the game */
13760       if (currentMove < forwardMostMove) {
13761         SetTrainingModeOn();
13762         DisplayMessage("", _("Training mode on"));
13763       } else {
13764         gameMode = PlayFromGameFile;
13765         DisplayError(_("Already at end of game"), 0);
13766       }
13767     }
13768     ModeHighlight();
13769 }
13770
13771 void
13772 IcsClientEvent ()
13773 {
13774     if (!appData.icsActive) return;
13775     switch (gameMode) {
13776       case IcsPlayingWhite:
13777       case IcsPlayingBlack:
13778       case IcsObserving:
13779       case IcsIdle:
13780       case BeginningOfGame:
13781       case IcsExamining:
13782         return;
13783
13784       case EditGame:
13785         break;
13786
13787       case EditPosition:
13788         EditPositionDone(TRUE);
13789         break;
13790
13791       case AnalyzeMode:
13792       case AnalyzeFile:
13793         ExitAnalyzeMode();
13794         break;
13795
13796       default:
13797         EditGameEvent();
13798         break;
13799     }
13800
13801     gameMode = IcsIdle;
13802     ModeHighlight();
13803     return;
13804 }
13805
13806 void
13807 EditGameEvent ()
13808 {
13809     int i;
13810
13811     switch (gameMode) {
13812       case Training:
13813         SetTrainingModeOff();
13814         break;
13815       case MachinePlaysWhite:
13816       case MachinePlaysBlack:
13817       case BeginningOfGame:
13818         SendToProgram("force\n", &first);
13819         SetUserThinkingEnables();
13820         break;
13821       case PlayFromGameFile:
13822         (void) StopLoadGameTimer();
13823         if (gameFileFP != NULL) {
13824             gameFileFP = NULL;
13825         }
13826         break;
13827       case EditPosition:
13828         EditPositionDone(TRUE);
13829         break;
13830       case AnalyzeMode:
13831       case AnalyzeFile:
13832         ExitAnalyzeMode();
13833         SendToProgram("force\n", &first);
13834         break;
13835       case TwoMachinesPlay:
13836         GameEnds(EndOfFile, NULL, GE_PLAYER);
13837         ResurrectChessProgram();
13838         SetUserThinkingEnables();
13839         break;
13840       case EndOfGame:
13841         ResurrectChessProgram();
13842         break;
13843       case IcsPlayingBlack:
13844       case IcsPlayingWhite:
13845         DisplayError(_("Warning: You are still playing a game"), 0);
13846         break;
13847       case IcsObserving:
13848         DisplayError(_("Warning: You are still observing a game"), 0);
13849         break;
13850       case IcsExamining:
13851         DisplayError(_("Warning: You are still examining a game"), 0);
13852         break;
13853       case IcsIdle:
13854         break;
13855       case EditGame:
13856       default:
13857         return;
13858     }
13859
13860     pausing = FALSE;
13861     StopClocks();
13862     first.offeredDraw = second.offeredDraw = 0;
13863
13864     if (gameMode == PlayFromGameFile) {
13865         whiteTimeRemaining = timeRemaining[0][currentMove];
13866         blackTimeRemaining = timeRemaining[1][currentMove];
13867         DisplayTitle("");
13868     }
13869
13870     if (gameMode == MachinePlaysWhite ||
13871         gameMode == MachinePlaysBlack ||
13872         gameMode == TwoMachinesPlay ||
13873         gameMode == EndOfGame) {
13874         i = forwardMostMove;
13875         while (i > currentMove) {
13876             SendToProgram("undo\n", &first);
13877             i--;
13878         }
13879         if(!adjustedClock) {
13880         whiteTimeRemaining = timeRemaining[0][currentMove];
13881         blackTimeRemaining = timeRemaining[1][currentMove];
13882         DisplayBothClocks();
13883         }
13884         if (whiteFlag || blackFlag) {
13885             whiteFlag = blackFlag = 0;
13886         }
13887         DisplayTitle("");
13888     }
13889
13890     gameMode = EditGame;
13891     ModeHighlight();
13892     SetGameInfo();
13893 }
13894
13895
13896 void
13897 EditPositionEvent ()
13898 {
13899     if (gameMode == EditPosition) {
13900         EditGameEvent();
13901         return;
13902     }
13903
13904     EditGameEvent();
13905     if (gameMode != EditGame) return;
13906
13907     gameMode = EditPosition;
13908     ModeHighlight();
13909     SetGameInfo();
13910     if (currentMove > 0)
13911       CopyBoard(boards[0], boards[currentMove]);
13912
13913     blackPlaysFirst = !WhiteOnMove(currentMove);
13914     ResetClocks();
13915     currentMove = forwardMostMove = backwardMostMove = 0;
13916     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13917     DisplayMove(-1);
13918     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13919 }
13920
13921 void
13922 ExitAnalyzeMode ()
13923 {
13924     /* [DM] icsEngineAnalyze - possible call from other functions */
13925     if (appData.icsEngineAnalyze) {
13926         appData.icsEngineAnalyze = FALSE;
13927
13928         DisplayMessage("",_("Close ICS engine analyze..."));
13929     }
13930     if (first.analysisSupport && first.analyzing) {
13931       SendToProgram("exit\n", &first);
13932       first.analyzing = FALSE;
13933     }
13934     thinkOutput[0] = NULLCHAR;
13935 }
13936
13937 void
13938 EditPositionDone (Boolean fakeRights)
13939 {
13940     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13941
13942     startedFromSetupPosition = TRUE;
13943     InitChessProgram(&first, FALSE);
13944     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13945       boards[0][EP_STATUS] = EP_NONE;
13946       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13947       if(boards[0][0][BOARD_WIDTH>>1] == king) {
13948         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
13949         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13950       } else boards[0][CASTLING][2] = NoRights;
13951       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13952         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
13953         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13954       } else boards[0][CASTLING][5] = NoRights;
13955       if(gameInfo.variant == VariantSChess) {
13956         int i;
13957         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
13958           boards[0][VIRGIN][i] = 0;
13959           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
13960           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
13961         }
13962       }
13963     }
13964     SendToProgram("force\n", &first);
13965     if (blackPlaysFirst) {
13966         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13967         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13968         currentMove = forwardMostMove = backwardMostMove = 1;
13969         CopyBoard(boards[1], boards[0]);
13970     } else {
13971         currentMove = forwardMostMove = backwardMostMove = 0;
13972     }
13973     SendBoard(&first, forwardMostMove);
13974     if (appData.debugMode) {
13975         fprintf(debugFP, "EditPosDone\n");
13976     }
13977     DisplayTitle("");
13978     DisplayMessage("", "");
13979     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13980     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13981     gameMode = EditGame;
13982     ModeHighlight();
13983     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13984     ClearHighlights(); /* [AS] */
13985 }
13986
13987 /* Pause for `ms' milliseconds */
13988 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13989 void
13990 TimeDelay (long ms)
13991 {
13992     TimeMark m1, m2;
13993
13994     GetTimeMark(&m1);
13995     do {
13996         GetTimeMark(&m2);
13997     } while (SubtractTimeMarks(&m2, &m1) < ms);
13998 }
13999
14000 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14001 void
14002 SendMultiLineToICS (char *buf)
14003 {
14004     char temp[MSG_SIZ+1], *p;
14005     int len;
14006
14007     len = strlen(buf);
14008     if (len > MSG_SIZ)
14009       len = MSG_SIZ;
14010
14011     strncpy(temp, buf, len);
14012     temp[len] = 0;
14013
14014     p = temp;
14015     while (*p) {
14016         if (*p == '\n' || *p == '\r')
14017           *p = ' ';
14018         ++p;
14019     }
14020
14021     strcat(temp, "\n");
14022     SendToICS(temp);
14023     SendToPlayer(temp, strlen(temp));
14024 }
14025
14026 void
14027 SetWhiteToPlayEvent ()
14028 {
14029     if (gameMode == EditPosition) {
14030         blackPlaysFirst = FALSE;
14031         DisplayBothClocks();    /* works because currentMove is 0 */
14032     } else if (gameMode == IcsExamining) {
14033         SendToICS(ics_prefix);
14034         SendToICS("tomove white\n");
14035     }
14036 }
14037
14038 void
14039 SetBlackToPlayEvent ()
14040 {
14041     if (gameMode == EditPosition) {
14042         blackPlaysFirst = TRUE;
14043         currentMove = 1;        /* kludge */
14044         DisplayBothClocks();
14045         currentMove = 0;
14046     } else if (gameMode == IcsExamining) {
14047         SendToICS(ics_prefix);
14048         SendToICS("tomove black\n");
14049     }
14050 }
14051
14052 void
14053 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14054 {
14055     char buf[MSG_SIZ];
14056     ChessSquare piece = boards[0][y][x];
14057
14058     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14059
14060     switch (selection) {
14061       case ClearBoard:
14062         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14063             SendToICS(ics_prefix);
14064             SendToICS("bsetup clear\n");
14065         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14066             SendToICS(ics_prefix);
14067             SendToICS("clearboard\n");
14068         } else {
14069             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14070                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14071                 for (y = 0; y < BOARD_HEIGHT; y++) {
14072                     if (gameMode == IcsExamining) {
14073                         if (boards[currentMove][y][x] != EmptySquare) {
14074                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14075                                     AAA + x, ONE + y);
14076                             SendToICS(buf);
14077                         }
14078                     } else {
14079                         boards[0][y][x] = p;
14080                     }
14081                 }
14082             }
14083         }
14084         if (gameMode == EditPosition) {
14085             DrawPosition(FALSE, boards[0]);
14086         }
14087         break;
14088
14089       case WhitePlay:
14090         SetWhiteToPlayEvent();
14091         break;
14092
14093       case BlackPlay:
14094         SetBlackToPlayEvent();
14095         break;
14096
14097       case EmptySquare:
14098         if (gameMode == IcsExamining) {
14099             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14100             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14101             SendToICS(buf);
14102         } else {
14103             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14104                 if(x == BOARD_LEFT-2) {
14105                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14106                     boards[0][y][1] = 0;
14107                 } else
14108                 if(x == BOARD_RGHT+1) {
14109                     if(y >= gameInfo.holdingsSize) break;
14110                     boards[0][y][BOARD_WIDTH-2] = 0;
14111                 } else break;
14112             }
14113             boards[0][y][x] = EmptySquare;
14114             DrawPosition(FALSE, boards[0]);
14115         }
14116         break;
14117
14118       case PromotePiece:
14119         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14120            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14121             selection = (ChessSquare) (PROMOTED piece);
14122         } else if(piece == EmptySquare) selection = WhiteSilver;
14123         else selection = (ChessSquare)((int)piece - 1);
14124         goto defaultlabel;
14125
14126       case DemotePiece:
14127         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14128            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14129             selection = (ChessSquare) (DEMOTED piece);
14130         } else if(piece == EmptySquare) selection = BlackSilver;
14131         else selection = (ChessSquare)((int)piece + 1);
14132         goto defaultlabel;
14133
14134       case WhiteQueen:
14135       case BlackQueen:
14136         if(gameInfo.variant == VariantShatranj ||
14137            gameInfo.variant == VariantXiangqi  ||
14138            gameInfo.variant == VariantCourier  ||
14139            gameInfo.variant == VariantMakruk     )
14140             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14141         goto defaultlabel;
14142
14143       case WhiteKing:
14144       case BlackKing:
14145         if(gameInfo.variant == VariantXiangqi)
14146             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14147         if(gameInfo.variant == VariantKnightmate)
14148             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14149       default:
14150         defaultlabel:
14151         if (gameMode == IcsExamining) {
14152             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14153             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14154                      PieceToChar(selection), AAA + x, ONE + y);
14155             SendToICS(buf);
14156         } else {
14157             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14158                 int n;
14159                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14160                     n = PieceToNumber(selection - BlackPawn);
14161                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14162                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14163                     boards[0][BOARD_HEIGHT-1-n][1]++;
14164                 } else
14165                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14166                     n = PieceToNumber(selection);
14167                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14168                     boards[0][n][BOARD_WIDTH-1] = selection;
14169                     boards[0][n][BOARD_WIDTH-2]++;
14170                 }
14171             } else
14172             boards[0][y][x] = selection;
14173             DrawPosition(TRUE, boards[0]);
14174             ClearHighlights();
14175             fromX = fromY = -1;
14176         }
14177         break;
14178     }
14179 }
14180
14181
14182 void
14183 DropMenuEvent (ChessSquare selection, int x, int y)
14184 {
14185     ChessMove moveType;
14186
14187     switch (gameMode) {
14188       case IcsPlayingWhite:
14189       case MachinePlaysBlack:
14190         if (!WhiteOnMove(currentMove)) {
14191             DisplayMoveError(_("It is Black's turn"));
14192             return;
14193         }
14194         moveType = WhiteDrop;
14195         break;
14196       case IcsPlayingBlack:
14197       case MachinePlaysWhite:
14198         if (WhiteOnMove(currentMove)) {
14199             DisplayMoveError(_("It is White's turn"));
14200             return;
14201         }
14202         moveType = BlackDrop;
14203         break;
14204       case EditGame:
14205         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14206         break;
14207       default:
14208         return;
14209     }
14210
14211     if (moveType == BlackDrop && selection < BlackPawn) {
14212       selection = (ChessSquare) ((int) selection
14213                                  + (int) BlackPawn - (int) WhitePawn);
14214     }
14215     if (boards[currentMove][y][x] != EmptySquare) {
14216         DisplayMoveError(_("That square is occupied"));
14217         return;
14218     }
14219
14220     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14221 }
14222
14223 void
14224 AcceptEvent ()
14225 {
14226     /* Accept a pending offer of any kind from opponent */
14227
14228     if (appData.icsActive) {
14229         SendToICS(ics_prefix);
14230         SendToICS("accept\n");
14231     } else if (cmailMsgLoaded) {
14232         if (currentMove == cmailOldMove &&
14233             commentList[cmailOldMove] != NULL &&
14234             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14235                    "Black offers a draw" : "White offers a draw")) {
14236             TruncateGame();
14237             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14238             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14239         } else {
14240             DisplayError(_("There is no pending offer on this move"), 0);
14241             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14242         }
14243     } else {
14244         /* Not used for offers from chess program */
14245     }
14246 }
14247
14248 void
14249 DeclineEvent ()
14250 {
14251     /* Decline a pending offer of any kind from opponent */
14252
14253     if (appData.icsActive) {
14254         SendToICS(ics_prefix);
14255         SendToICS("decline\n");
14256     } else if (cmailMsgLoaded) {
14257         if (currentMove == cmailOldMove &&
14258             commentList[cmailOldMove] != NULL &&
14259             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14260                    "Black offers a draw" : "White offers a draw")) {
14261 #ifdef NOTDEF
14262             AppendComment(cmailOldMove, "Draw declined", TRUE);
14263             DisplayComment(cmailOldMove - 1, "Draw declined");
14264 #endif /*NOTDEF*/
14265         } else {
14266             DisplayError(_("There is no pending offer on this move"), 0);
14267         }
14268     } else {
14269         /* Not used for offers from chess program */
14270     }
14271 }
14272
14273 void
14274 RematchEvent ()
14275 {
14276     /* Issue ICS rematch command */
14277     if (appData.icsActive) {
14278         SendToICS(ics_prefix);
14279         SendToICS("rematch\n");
14280     }
14281 }
14282
14283 void
14284 CallFlagEvent ()
14285 {
14286     /* Call your opponent's flag (claim a win on time) */
14287     if (appData.icsActive) {
14288         SendToICS(ics_prefix);
14289         SendToICS("flag\n");
14290     } else {
14291         switch (gameMode) {
14292           default:
14293             return;
14294           case MachinePlaysWhite:
14295             if (whiteFlag) {
14296                 if (blackFlag)
14297                   GameEnds(GameIsDrawn, "Both players ran out of time",
14298                            GE_PLAYER);
14299                 else
14300                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14301             } else {
14302                 DisplayError(_("Your opponent is not out of time"), 0);
14303             }
14304             break;
14305           case MachinePlaysBlack:
14306             if (blackFlag) {
14307                 if (whiteFlag)
14308                   GameEnds(GameIsDrawn, "Both players ran out of time",
14309                            GE_PLAYER);
14310                 else
14311                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14312             } else {
14313                 DisplayError(_("Your opponent is not out of time"), 0);
14314             }
14315             break;
14316         }
14317     }
14318 }
14319
14320 void
14321 ClockClick (int which)
14322 {       // [HGM] code moved to back-end from winboard.c
14323         if(which) { // black clock
14324           if (gameMode == EditPosition || gameMode == IcsExamining) {
14325             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14326             SetBlackToPlayEvent();
14327           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14328           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14329           } else if (shiftKey) {
14330             AdjustClock(which, -1);
14331           } else if (gameMode == IcsPlayingWhite ||
14332                      gameMode == MachinePlaysBlack) {
14333             CallFlagEvent();
14334           }
14335         } else { // white clock
14336           if (gameMode == EditPosition || gameMode == IcsExamining) {
14337             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14338             SetWhiteToPlayEvent();
14339           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14340           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14341           } else if (shiftKey) {
14342             AdjustClock(which, -1);
14343           } else if (gameMode == IcsPlayingBlack ||
14344                    gameMode == MachinePlaysWhite) {
14345             CallFlagEvent();
14346           }
14347         }
14348 }
14349
14350 void
14351 DrawEvent ()
14352 {
14353     /* Offer draw or accept pending draw offer from opponent */
14354
14355     if (appData.icsActive) {
14356         /* Note: tournament rules require draw offers to be
14357            made after you make your move but before you punch
14358            your clock.  Currently ICS doesn't let you do that;
14359            instead, you immediately punch your clock after making
14360            a move, but you can offer a draw at any time. */
14361
14362         SendToICS(ics_prefix);
14363         SendToICS("draw\n");
14364         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14365     } else if (cmailMsgLoaded) {
14366         if (currentMove == cmailOldMove &&
14367             commentList[cmailOldMove] != NULL &&
14368             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14369                    "Black offers a draw" : "White offers a draw")) {
14370             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14371             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14372         } else if (currentMove == cmailOldMove + 1) {
14373             char *offer = WhiteOnMove(cmailOldMove) ?
14374               "White offers a draw" : "Black offers a draw";
14375             AppendComment(currentMove, offer, TRUE);
14376             DisplayComment(currentMove - 1, offer);
14377             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14378         } else {
14379             DisplayError(_("You must make your move before offering a draw"), 0);
14380             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14381         }
14382     } else if (first.offeredDraw) {
14383         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14384     } else {
14385         if (first.sendDrawOffers) {
14386             SendToProgram("draw\n", &first);
14387             userOfferedDraw = TRUE;
14388         }
14389     }
14390 }
14391
14392 void
14393 AdjournEvent ()
14394 {
14395     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14396
14397     if (appData.icsActive) {
14398         SendToICS(ics_prefix);
14399         SendToICS("adjourn\n");
14400     } else {
14401         /* Currently GNU Chess doesn't offer or accept Adjourns */
14402     }
14403 }
14404
14405
14406 void
14407 AbortEvent ()
14408 {
14409     /* Offer Abort or accept pending Abort offer from opponent */
14410
14411     if (appData.icsActive) {
14412         SendToICS(ics_prefix);
14413         SendToICS("abort\n");
14414     } else {
14415         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14416     }
14417 }
14418
14419 void
14420 ResignEvent ()
14421 {
14422     /* Resign.  You can do this even if it's not your turn. */
14423
14424     if (appData.icsActive) {
14425         SendToICS(ics_prefix);
14426         SendToICS("resign\n");
14427     } else {
14428         switch (gameMode) {
14429           case MachinePlaysWhite:
14430             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14431             break;
14432           case MachinePlaysBlack:
14433             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14434             break;
14435           case EditGame:
14436             if (cmailMsgLoaded) {
14437                 TruncateGame();
14438                 if (WhiteOnMove(cmailOldMove)) {
14439                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14440                 } else {
14441                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14442                 }
14443                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14444             }
14445             break;
14446           default:
14447             break;
14448         }
14449     }
14450 }
14451
14452
14453 void
14454 StopObservingEvent ()
14455 {
14456     /* Stop observing current games */
14457     SendToICS(ics_prefix);
14458     SendToICS("unobserve\n");
14459 }
14460
14461 void
14462 StopExaminingEvent ()
14463 {
14464     /* Stop observing current game */
14465     SendToICS(ics_prefix);
14466     SendToICS("unexamine\n");
14467 }
14468
14469 void
14470 ForwardInner (int target)
14471 {
14472     int limit; int oldSeekGraphUp = seekGraphUp;
14473
14474     if (appData.debugMode)
14475         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14476                 target, currentMove, forwardMostMove);
14477
14478     if (gameMode == EditPosition)
14479       return;
14480
14481     seekGraphUp = FALSE;
14482     MarkTargetSquares(1);
14483
14484     if (gameMode == PlayFromGameFile && !pausing)
14485       PauseEvent();
14486
14487     if (gameMode == IcsExamining && pausing)
14488       limit = pauseExamForwardMostMove;
14489     else
14490       limit = forwardMostMove;
14491
14492     if (target > limit) target = limit;
14493
14494     if (target > 0 && moveList[target - 1][0]) {
14495         int fromX, fromY, toX, toY;
14496         toX = moveList[target - 1][2] - AAA;
14497         toY = moveList[target - 1][3] - ONE;
14498         if (moveList[target - 1][1] == '@') {
14499             if (appData.highlightLastMove) {
14500                 SetHighlights(-1, -1, toX, toY);
14501             }
14502         } else {
14503             fromX = moveList[target - 1][0] - AAA;
14504             fromY = moveList[target - 1][1] - ONE;
14505             if (target == currentMove + 1) {
14506                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14507             }
14508             if (appData.highlightLastMove) {
14509                 SetHighlights(fromX, fromY, toX, toY);
14510             }
14511         }
14512     }
14513     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14514         gameMode == Training || gameMode == PlayFromGameFile ||
14515         gameMode == AnalyzeFile) {
14516         while (currentMove < target) {
14517             SendMoveToProgram(currentMove++, &first);
14518         }
14519     } else {
14520         currentMove = target;
14521     }
14522
14523     if (gameMode == EditGame || gameMode == EndOfGame) {
14524         whiteTimeRemaining = timeRemaining[0][currentMove];
14525         blackTimeRemaining = timeRemaining[1][currentMove];
14526     }
14527     DisplayBothClocks();
14528     DisplayMove(currentMove - 1);
14529     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14530     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14531     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14532         DisplayComment(currentMove - 1, commentList[currentMove]);
14533     }
14534     ClearMap(); // [HGM] exclude: invalidate map
14535 }
14536
14537
14538 void
14539 ForwardEvent ()
14540 {
14541     if (gameMode == IcsExamining && !pausing) {
14542         SendToICS(ics_prefix);
14543         SendToICS("forward\n");
14544     } else {
14545         ForwardInner(currentMove + 1);
14546     }
14547 }
14548
14549 void
14550 ToEndEvent ()
14551 {
14552     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14553         /* to optimze, we temporarily turn off analysis mode while we feed
14554          * the remaining moves to the engine. Otherwise we get analysis output
14555          * after each move.
14556          */
14557         if (first.analysisSupport) {
14558           SendToProgram("exit\nforce\n", &first);
14559           first.analyzing = FALSE;
14560         }
14561     }
14562
14563     if (gameMode == IcsExamining && !pausing) {
14564         SendToICS(ics_prefix);
14565         SendToICS("forward 999999\n");
14566     } else {
14567         ForwardInner(forwardMostMove);
14568     }
14569
14570     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14571         /* we have fed all the moves, so reactivate analysis mode */
14572         SendToProgram("analyze\n", &first);
14573         first.analyzing = TRUE;
14574         /*first.maybeThinking = TRUE;*/
14575         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14576     }
14577 }
14578
14579 void
14580 BackwardInner (int target)
14581 {
14582     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14583
14584     if (appData.debugMode)
14585         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14586                 target, currentMove, forwardMostMove);
14587
14588     if (gameMode == EditPosition) return;
14589     seekGraphUp = FALSE;
14590     MarkTargetSquares(1);
14591     if (currentMove <= backwardMostMove) {
14592         ClearHighlights();
14593         DrawPosition(full_redraw, boards[currentMove]);
14594         return;
14595     }
14596     if (gameMode == PlayFromGameFile && !pausing)
14597       PauseEvent();
14598
14599     if (moveList[target][0]) {
14600         int fromX, fromY, toX, toY;
14601         toX = moveList[target][2] - AAA;
14602         toY = moveList[target][3] - ONE;
14603         if (moveList[target][1] == '@') {
14604             if (appData.highlightLastMove) {
14605                 SetHighlights(-1, -1, toX, toY);
14606             }
14607         } else {
14608             fromX = moveList[target][0] - AAA;
14609             fromY = moveList[target][1] - ONE;
14610             if (target == currentMove - 1) {
14611                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14612             }
14613             if (appData.highlightLastMove) {
14614                 SetHighlights(fromX, fromY, toX, toY);
14615             }
14616         }
14617     }
14618     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14619         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14620         while (currentMove > target) {
14621             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14622                 // null move cannot be undone. Reload program with move history before it.
14623                 int i;
14624                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14625                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14626                 }
14627                 SendBoard(&first, i); 
14628                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14629                 break;
14630             }
14631             SendToProgram("undo\n", &first);
14632             currentMove--;
14633         }
14634     } else {
14635         currentMove = target;
14636     }
14637
14638     if (gameMode == EditGame || gameMode == EndOfGame) {
14639         whiteTimeRemaining = timeRemaining[0][currentMove];
14640         blackTimeRemaining = timeRemaining[1][currentMove];
14641     }
14642     DisplayBothClocks();
14643     DisplayMove(currentMove - 1);
14644     DrawPosition(full_redraw, boards[currentMove]);
14645     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14646     // [HGM] PV info: routine tests if comment empty
14647     DisplayComment(currentMove - 1, commentList[currentMove]);
14648     ClearMap(); // [HGM] exclude: invalidate map
14649 }
14650
14651 void
14652 BackwardEvent ()
14653 {
14654     if (gameMode == IcsExamining && !pausing) {
14655         SendToICS(ics_prefix);
14656         SendToICS("backward\n");
14657     } else {
14658         BackwardInner(currentMove - 1);
14659     }
14660 }
14661
14662 void
14663 ToStartEvent ()
14664 {
14665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14666         /* to optimize, we temporarily turn off analysis mode while we undo
14667          * all the moves. Otherwise we get analysis output after each undo.
14668          */
14669         if (first.analysisSupport) {
14670           SendToProgram("exit\nforce\n", &first);
14671           first.analyzing = FALSE;
14672         }
14673     }
14674
14675     if (gameMode == IcsExamining && !pausing) {
14676         SendToICS(ics_prefix);
14677         SendToICS("backward 999999\n");
14678     } else {
14679         BackwardInner(backwardMostMove);
14680     }
14681
14682     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14683         /* we have fed all the moves, so reactivate analysis mode */
14684         SendToProgram("analyze\n", &first);
14685         first.analyzing = TRUE;
14686         /*first.maybeThinking = TRUE;*/
14687         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14688     }
14689 }
14690
14691 void
14692 ToNrEvent (int to)
14693 {
14694   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14695   if (to >= forwardMostMove) to = forwardMostMove;
14696   if (to <= backwardMostMove) to = backwardMostMove;
14697   if (to < currentMove) {
14698     BackwardInner(to);
14699   } else {
14700     ForwardInner(to);
14701   }
14702 }
14703
14704 void
14705 RevertEvent (Boolean annotate)
14706 {
14707     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14708         return;
14709     }
14710     if (gameMode != IcsExamining) {
14711         DisplayError(_("You are not examining a game"), 0);
14712         return;
14713     }
14714     if (pausing) {
14715         DisplayError(_("You can't revert while pausing"), 0);
14716         return;
14717     }
14718     SendToICS(ics_prefix);
14719     SendToICS("revert\n");
14720 }
14721
14722 void
14723 RetractMoveEvent ()
14724 {
14725     switch (gameMode) {
14726       case MachinePlaysWhite:
14727       case MachinePlaysBlack:
14728         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14729             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14730             return;
14731         }
14732         if (forwardMostMove < 2) return;
14733         currentMove = forwardMostMove = forwardMostMove - 2;
14734         whiteTimeRemaining = timeRemaining[0][currentMove];
14735         blackTimeRemaining = timeRemaining[1][currentMove];
14736         DisplayBothClocks();
14737         DisplayMove(currentMove - 1);
14738         ClearHighlights();/*!! could figure this out*/
14739         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14740         SendToProgram("remove\n", &first);
14741         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14742         break;
14743
14744       case BeginningOfGame:
14745       default:
14746         break;
14747
14748       case IcsPlayingWhite:
14749       case IcsPlayingBlack:
14750         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14751             SendToICS(ics_prefix);
14752             SendToICS("takeback 2\n");
14753         } else {
14754             SendToICS(ics_prefix);
14755             SendToICS("takeback 1\n");
14756         }
14757         break;
14758     }
14759 }
14760
14761 void
14762 MoveNowEvent ()
14763 {
14764     ChessProgramState *cps;
14765
14766     switch (gameMode) {
14767       case MachinePlaysWhite:
14768         if (!WhiteOnMove(forwardMostMove)) {
14769             DisplayError(_("It is your turn"), 0);
14770             return;
14771         }
14772         cps = &first;
14773         break;
14774       case MachinePlaysBlack:
14775         if (WhiteOnMove(forwardMostMove)) {
14776             DisplayError(_("It is your turn"), 0);
14777             return;
14778         }
14779         cps = &first;
14780         break;
14781       case TwoMachinesPlay:
14782         if (WhiteOnMove(forwardMostMove) ==
14783             (first.twoMachinesColor[0] == 'w')) {
14784             cps = &first;
14785         } else {
14786             cps = &second;
14787         }
14788         break;
14789       case BeginningOfGame:
14790       default:
14791         return;
14792     }
14793     SendToProgram("?\n", cps);
14794 }
14795
14796 void
14797 TruncateGameEvent ()
14798 {
14799     EditGameEvent();
14800     if (gameMode != EditGame) return;
14801     TruncateGame();
14802 }
14803
14804 void
14805 TruncateGame ()
14806 {
14807     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14808     if (forwardMostMove > currentMove) {
14809         if (gameInfo.resultDetails != NULL) {
14810             free(gameInfo.resultDetails);
14811             gameInfo.resultDetails = NULL;
14812             gameInfo.result = GameUnfinished;
14813         }
14814         forwardMostMove = currentMove;
14815         HistorySet(parseList, backwardMostMove, forwardMostMove,
14816                    currentMove-1);
14817     }
14818 }
14819
14820 void
14821 HintEvent ()
14822 {
14823     if (appData.noChessProgram) return;
14824     switch (gameMode) {
14825       case MachinePlaysWhite:
14826         if (WhiteOnMove(forwardMostMove)) {
14827             DisplayError(_("Wait until your turn"), 0);
14828             return;
14829         }
14830         break;
14831       case BeginningOfGame:
14832       case MachinePlaysBlack:
14833         if (!WhiteOnMove(forwardMostMove)) {
14834             DisplayError(_("Wait until your turn"), 0);
14835             return;
14836         }
14837         break;
14838       default:
14839         DisplayError(_("No hint available"), 0);
14840         return;
14841     }
14842     SendToProgram("hint\n", &first);
14843     hintRequested = TRUE;
14844 }
14845
14846 void
14847 BookEvent ()
14848 {
14849     if (appData.noChessProgram) return;
14850     switch (gameMode) {
14851       case MachinePlaysWhite:
14852         if (WhiteOnMove(forwardMostMove)) {
14853             DisplayError(_("Wait until your turn"), 0);
14854             return;
14855         }
14856         break;
14857       case BeginningOfGame:
14858       case MachinePlaysBlack:
14859         if (!WhiteOnMove(forwardMostMove)) {
14860             DisplayError(_("Wait until your turn"), 0);
14861             return;
14862         }
14863         break;
14864       case EditPosition:
14865         EditPositionDone(TRUE);
14866         break;
14867       case TwoMachinesPlay:
14868         return;
14869       default:
14870         break;
14871     }
14872     SendToProgram("bk\n", &first);
14873     bookOutput[0] = NULLCHAR;
14874     bookRequested = TRUE;
14875 }
14876
14877 void
14878 AboutGameEvent ()
14879 {
14880     char *tags = PGNTags(&gameInfo);
14881     TagsPopUp(tags, CmailMsg());
14882     free(tags);
14883 }
14884
14885 /* end button procedures */
14886
14887 void
14888 PrintPosition (FILE *fp, int move)
14889 {
14890     int i, j;
14891
14892     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14893         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14894             char c = PieceToChar(boards[move][i][j]);
14895             fputc(c == 'x' ? '.' : c, fp);
14896             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14897         }
14898     }
14899     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14900       fprintf(fp, "white to play\n");
14901     else
14902       fprintf(fp, "black to play\n");
14903 }
14904
14905 void
14906 PrintOpponents (FILE *fp)
14907 {
14908     if (gameInfo.white != NULL) {
14909         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14910     } else {
14911         fprintf(fp, "\n");
14912     }
14913 }
14914
14915 /* Find last component of program's own name, using some heuristics */
14916 void
14917 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14918 {
14919     char *p, *q, c;
14920     int local = (strcmp(host, "localhost") == 0);
14921     while (!local && (p = strchr(prog, ';')) != NULL) {
14922         p++;
14923         while (*p == ' ') p++;
14924         prog = p;
14925     }
14926     if (*prog == '"' || *prog == '\'') {
14927         q = strchr(prog + 1, *prog);
14928     } else {
14929         q = strchr(prog, ' ');
14930     }
14931     if (q == NULL) q = prog + strlen(prog);
14932     p = q;
14933     while (p >= prog && *p != '/' && *p != '\\') p--;
14934     p++;
14935     if(p == prog && *p == '"') p++;
14936     c = *q; *q = 0;
14937     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14938     memcpy(buf, p, q - p);
14939     buf[q - p] = NULLCHAR;
14940     if (!local) {
14941         strcat(buf, "@");
14942         strcat(buf, host);
14943     }
14944 }
14945
14946 char *
14947 TimeControlTagValue ()
14948 {
14949     char buf[MSG_SIZ];
14950     if (!appData.clockMode) {
14951       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14952     } else if (movesPerSession > 0) {
14953       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14954     } else if (timeIncrement == 0) {
14955       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14956     } else {
14957       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14958     }
14959     return StrSave(buf);
14960 }
14961
14962 void
14963 SetGameInfo ()
14964 {
14965     /* This routine is used only for certain modes */
14966     VariantClass v = gameInfo.variant;
14967     ChessMove r = GameUnfinished;
14968     char *p = NULL;
14969
14970     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14971         r = gameInfo.result;
14972         p = gameInfo.resultDetails;
14973         gameInfo.resultDetails = NULL;
14974     }
14975     ClearGameInfo(&gameInfo);
14976     gameInfo.variant = v;
14977
14978     switch (gameMode) {
14979       case MachinePlaysWhite:
14980         gameInfo.event = StrSave( appData.pgnEventHeader );
14981         gameInfo.site = StrSave(HostName());
14982         gameInfo.date = PGNDate();
14983         gameInfo.round = StrSave("-");
14984         gameInfo.white = StrSave(first.tidy);
14985         gameInfo.black = StrSave(UserName());
14986         gameInfo.timeControl = TimeControlTagValue();
14987         break;
14988
14989       case MachinePlaysBlack:
14990         gameInfo.event = StrSave( appData.pgnEventHeader );
14991         gameInfo.site = StrSave(HostName());
14992         gameInfo.date = PGNDate();
14993         gameInfo.round = StrSave("-");
14994         gameInfo.white = StrSave(UserName());
14995         gameInfo.black = StrSave(first.tidy);
14996         gameInfo.timeControl = TimeControlTagValue();
14997         break;
14998
14999       case TwoMachinesPlay:
15000         gameInfo.event = StrSave( appData.pgnEventHeader );
15001         gameInfo.site = StrSave(HostName());
15002         gameInfo.date = PGNDate();
15003         if (roundNr > 0) {
15004             char buf[MSG_SIZ];
15005             snprintf(buf, MSG_SIZ, "%d", roundNr);
15006             gameInfo.round = StrSave(buf);
15007         } else {
15008             gameInfo.round = StrSave("-");
15009         }
15010         if (first.twoMachinesColor[0] == 'w') {
15011             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15012             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15013         } else {
15014             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15015             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15016         }
15017         gameInfo.timeControl = TimeControlTagValue();
15018         break;
15019
15020       case EditGame:
15021         gameInfo.event = StrSave("Edited game");
15022         gameInfo.site = StrSave(HostName());
15023         gameInfo.date = PGNDate();
15024         gameInfo.round = StrSave("-");
15025         gameInfo.white = StrSave("-");
15026         gameInfo.black = StrSave("-");
15027         gameInfo.result = r;
15028         gameInfo.resultDetails = p;
15029         break;
15030
15031       case EditPosition:
15032         gameInfo.event = StrSave("Edited position");
15033         gameInfo.site = StrSave(HostName());
15034         gameInfo.date = PGNDate();
15035         gameInfo.round = StrSave("-");
15036         gameInfo.white = StrSave("-");
15037         gameInfo.black = StrSave("-");
15038         break;
15039
15040       case IcsPlayingWhite:
15041       case IcsPlayingBlack:
15042       case IcsObserving:
15043       case IcsExamining:
15044         break;
15045
15046       case PlayFromGameFile:
15047         gameInfo.event = StrSave("Game from non-PGN file");
15048         gameInfo.site = StrSave(HostName());
15049         gameInfo.date = PGNDate();
15050         gameInfo.round = StrSave("-");
15051         gameInfo.white = StrSave("?");
15052         gameInfo.black = StrSave("?");
15053         break;
15054
15055       default:
15056         break;
15057     }
15058 }
15059
15060 void
15061 ReplaceComment (int index, char *text)
15062 {
15063     int len;
15064     char *p;
15065     float score;
15066
15067     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15068        pvInfoList[index-1].depth == len &&
15069        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15070        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15071     while (*text == '\n') text++;
15072     len = strlen(text);
15073     while (len > 0 && text[len - 1] == '\n') len--;
15074
15075     if (commentList[index] != NULL)
15076       free(commentList[index]);
15077
15078     if (len == 0) {
15079         commentList[index] = NULL;
15080         return;
15081     }
15082   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15083       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15084       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15085     commentList[index] = (char *) malloc(len + 2);
15086     strncpy(commentList[index], text, len);
15087     commentList[index][len] = '\n';
15088     commentList[index][len + 1] = NULLCHAR;
15089   } else {
15090     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15091     char *p;
15092     commentList[index] = (char *) malloc(len + 7);
15093     safeStrCpy(commentList[index], "{\n", 3);
15094     safeStrCpy(commentList[index]+2, text, len+1);
15095     commentList[index][len+2] = NULLCHAR;
15096     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15097     strcat(commentList[index], "\n}\n");
15098   }
15099 }
15100
15101 void
15102 CrushCRs (char *text)
15103 {
15104   char *p = text;
15105   char *q = text;
15106   char ch;
15107
15108   do {
15109     ch = *p++;
15110     if (ch == '\r') continue;
15111     *q++ = ch;
15112   } while (ch != '\0');
15113 }
15114
15115 void
15116 AppendComment (int index, char *text, Boolean addBraces)
15117 /* addBraces  tells if we should add {} */
15118 {
15119     int oldlen, len;
15120     char *old;
15121
15122 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15123     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15124
15125     CrushCRs(text);
15126     while (*text == '\n') text++;
15127     len = strlen(text);
15128     while (len > 0 && text[len - 1] == '\n') len--;
15129     text[len] = NULLCHAR;
15130
15131     if (len == 0) return;
15132
15133     if (commentList[index] != NULL) {
15134       Boolean addClosingBrace = addBraces;
15135         old = commentList[index];
15136         oldlen = strlen(old);
15137         while(commentList[index][oldlen-1] ==  '\n')
15138           commentList[index][--oldlen] = NULLCHAR;
15139         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15140         safeStrCpy(commentList[index], old, oldlen + len + 6);
15141         free(old);
15142         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15143         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15144           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15145           while (*text == '\n') { text++; len--; }
15146           commentList[index][--oldlen] = NULLCHAR;
15147       }
15148         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15149         else          strcat(commentList[index], "\n");
15150         strcat(commentList[index], text);
15151         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15152         else          strcat(commentList[index], "\n");
15153     } else {
15154         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15155         if(addBraces)
15156           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15157         else commentList[index][0] = NULLCHAR;
15158         strcat(commentList[index], text);
15159         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15160         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15161     }
15162 }
15163
15164 static char *
15165 FindStr (char * text, char * sub_text)
15166 {
15167     char * result = strstr( text, sub_text );
15168
15169     if( result != NULL ) {
15170         result += strlen( sub_text );
15171     }
15172
15173     return result;
15174 }
15175
15176 /* [AS] Try to extract PV info from PGN comment */
15177 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15178 char *
15179 GetInfoFromComment (int index, char * text)
15180 {
15181     char * sep = text, *p;
15182
15183     if( text != NULL && index > 0 ) {
15184         int score = 0;
15185         int depth = 0;
15186         int time = -1, sec = 0, deci;
15187         char * s_eval = FindStr( text, "[%eval " );
15188         char * s_emt = FindStr( text, "[%emt " );
15189
15190         if( s_eval != NULL || s_emt != NULL ) {
15191             /* New style */
15192             char delim;
15193
15194             if( s_eval != NULL ) {
15195                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15196                     return text;
15197                 }
15198
15199                 if( delim != ']' ) {
15200                     return text;
15201                 }
15202             }
15203
15204             if( s_emt != NULL ) {
15205             }
15206                 return text;
15207         }
15208         else {
15209             /* We expect something like: [+|-]nnn.nn/dd */
15210             int score_lo = 0;
15211
15212             if(*text != '{') return text; // [HGM] braces: must be normal comment
15213
15214             sep = strchr( text, '/' );
15215             if( sep == NULL || sep < (text+4) ) {
15216                 return text;
15217             }
15218
15219             p = text;
15220             if(p[1] == '(') { // comment starts with PV
15221                p = strchr(p, ')'); // locate end of PV
15222                if(p == NULL || sep < p+5) return text;
15223                // at this point we have something like "{(.*) +0.23/6 ..."
15224                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15225                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15226                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15227             }
15228             time = -1; sec = -1; deci = -1;
15229             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15230                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15231                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15232                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15233                 return text;
15234             }
15235
15236             if( score_lo < 0 || score_lo >= 100 ) {
15237                 return text;
15238             }
15239
15240             if(sec >= 0) time = 600*time + 10*sec; else
15241             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15242
15243             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15244
15245             /* [HGM] PV time: now locate end of PV info */
15246             while( *++sep >= '0' && *sep <= '9'); // strip depth
15247             if(time >= 0)
15248             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15249             if(sec >= 0)
15250             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15251             if(deci >= 0)
15252             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15253             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15254         }
15255
15256         if( depth <= 0 ) {
15257             return text;
15258         }
15259
15260         if( time < 0 ) {
15261             time = -1;
15262         }
15263
15264         pvInfoList[index-1].depth = depth;
15265         pvInfoList[index-1].score = score;
15266         pvInfoList[index-1].time  = 10*time; // centi-sec
15267         if(*sep == '}') *sep = 0; else *--sep = '{';
15268         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15269     }
15270     return sep;
15271 }
15272
15273 void
15274 SendToProgram (char *message, ChessProgramState *cps)
15275 {
15276     int count, outCount, error;
15277     char buf[MSG_SIZ];
15278
15279     if (cps->pr == NoProc) return;
15280     Attention(cps);
15281
15282     if (appData.debugMode) {
15283         TimeMark now;
15284         GetTimeMark(&now);
15285         fprintf(debugFP, "%ld >%-6s: %s",
15286                 SubtractTimeMarks(&now, &programStartTime),
15287                 cps->which, message);
15288         if(serverFP)
15289             fprintf(serverFP, "%ld >%-6s: %s",
15290                 SubtractTimeMarks(&now, &programStartTime),
15291                 cps->which, message), fflush(serverFP);
15292     }
15293
15294     count = strlen(message);
15295     outCount = OutputToProcess(cps->pr, message, count, &error);
15296     if (outCount < count && !exiting
15297                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15298       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15299       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15300         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15301             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15302                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15303                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15304                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15305             } else {
15306                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15307                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15308                 gameInfo.result = res;
15309             }
15310             gameInfo.resultDetails = StrSave(buf);
15311         }
15312         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15313         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15314     }
15315 }
15316
15317 void
15318 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15319 {
15320     char *end_str;
15321     char buf[MSG_SIZ];
15322     ChessProgramState *cps = (ChessProgramState *)closure;
15323
15324     if (isr != cps->isr) return; /* Killed intentionally */
15325     if (count <= 0) {
15326         if (count == 0) {
15327             RemoveInputSource(cps->isr);
15328             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15329                     _(cps->which), cps->program);
15330             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15331             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15332                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15333                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15334                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15335                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15336                 } else {
15337                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15338                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15339                     gameInfo.result = res;
15340                 }
15341                 gameInfo.resultDetails = StrSave(buf);
15342             }
15343             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15344             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15345         } else {
15346             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15347                     _(cps->which), cps->program);
15348             RemoveInputSource(cps->isr);
15349
15350             /* [AS] Program is misbehaving badly... kill it */
15351             if( count == -2 ) {
15352                 DestroyChildProcess( cps->pr, 9 );
15353                 cps->pr = NoProc;
15354             }
15355
15356             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15357         }
15358         return;
15359     }
15360
15361     if ((end_str = strchr(message, '\r')) != NULL)
15362       *end_str = NULLCHAR;
15363     if ((end_str = strchr(message, '\n')) != NULL)
15364       *end_str = NULLCHAR;
15365
15366     if (appData.debugMode) {
15367         TimeMark now; int print = 1;
15368         char *quote = ""; char c; int i;
15369
15370         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15371                 char start = message[0];
15372                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15373                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15374                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15375                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15376                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15377                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15378                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15379                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15380                    sscanf(message, "hint: %c", &c)!=1 && 
15381                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15382                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15383                     print = (appData.engineComments >= 2);
15384                 }
15385                 message[0] = start; // restore original message
15386         }
15387         if(print) {
15388                 GetTimeMark(&now);
15389                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15390                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15391                         quote,
15392                         message);
15393                 if(serverFP)
15394                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15395                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15396                         quote,
15397                         message), fflush(serverFP);
15398         }
15399     }
15400
15401     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15402     if (appData.icsEngineAnalyze) {
15403         if (strstr(message, "whisper") != NULL ||
15404              strstr(message, "kibitz") != NULL ||
15405             strstr(message, "tellics") != NULL) return;
15406     }
15407
15408     HandleMachineMove(message, cps);
15409 }
15410
15411
15412 void
15413 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15414 {
15415     char buf[MSG_SIZ];
15416     int seconds;
15417
15418     if( timeControl_2 > 0 ) {
15419         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15420             tc = timeControl_2;
15421         }
15422     }
15423     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15424     inc /= cps->timeOdds;
15425     st  /= cps->timeOdds;
15426
15427     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15428
15429     if (st > 0) {
15430       /* Set exact time per move, normally using st command */
15431       if (cps->stKludge) {
15432         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15433         seconds = st % 60;
15434         if (seconds == 0) {
15435           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15436         } else {
15437           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15438         }
15439       } else {
15440         snprintf(buf, MSG_SIZ, "st %d\n", st);
15441       }
15442     } else {
15443       /* Set conventional or incremental time control, using level command */
15444       if (seconds == 0) {
15445         /* Note old gnuchess bug -- minutes:seconds used to not work.
15446            Fixed in later versions, but still avoid :seconds
15447            when seconds is 0. */
15448         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15449       } else {
15450         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15451                  seconds, inc/1000.);
15452       }
15453     }
15454     SendToProgram(buf, cps);
15455
15456     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15457     /* Orthogonally, limit search to given depth */
15458     if (sd > 0) {
15459       if (cps->sdKludge) {
15460         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15461       } else {
15462         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15463       }
15464       SendToProgram(buf, cps);
15465     }
15466
15467     if(cps->nps >= 0) { /* [HGM] nps */
15468         if(cps->supportsNPS == FALSE)
15469           cps->nps = -1; // don't use if engine explicitly says not supported!
15470         else {
15471           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15472           SendToProgram(buf, cps);
15473         }
15474     }
15475 }
15476
15477 ChessProgramState *
15478 WhitePlayer ()
15479 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15480 {
15481     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15482        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15483         return &second;
15484     return &first;
15485 }
15486
15487 void
15488 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15489 {
15490     char message[MSG_SIZ];
15491     long time, otime;
15492
15493     /* Note: this routine must be called when the clocks are stopped
15494        or when they have *just* been set or switched; otherwise
15495        it will be off by the time since the current tick started.
15496     */
15497     if (machineWhite) {
15498         time = whiteTimeRemaining / 10;
15499         otime = blackTimeRemaining / 10;
15500     } else {
15501         time = blackTimeRemaining / 10;
15502         otime = whiteTimeRemaining / 10;
15503     }
15504     /* [HGM] translate opponent's time by time-odds factor */
15505     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15506
15507     if (time <= 0) time = 1;
15508     if (otime <= 0) otime = 1;
15509
15510     snprintf(message, MSG_SIZ, "time %ld\n", time);
15511     SendToProgram(message, cps);
15512
15513     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15514     SendToProgram(message, cps);
15515 }
15516
15517 int
15518 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15519 {
15520   char buf[MSG_SIZ];
15521   int len = strlen(name);
15522   int val;
15523
15524   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15525     (*p) += len + 1;
15526     sscanf(*p, "%d", &val);
15527     *loc = (val != 0);
15528     while (**p && **p != ' ')
15529       (*p)++;
15530     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15531     SendToProgram(buf, cps);
15532     return TRUE;
15533   }
15534   return FALSE;
15535 }
15536
15537 int
15538 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15539 {
15540   char buf[MSG_SIZ];
15541   int len = strlen(name);
15542   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15543     (*p) += len + 1;
15544     sscanf(*p, "%d", loc);
15545     while (**p && **p != ' ') (*p)++;
15546     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15547     SendToProgram(buf, cps);
15548     return TRUE;
15549   }
15550   return FALSE;
15551 }
15552
15553 int
15554 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15555 {
15556   char buf[MSG_SIZ];
15557   int len = strlen(name);
15558   if (strncmp((*p), name, len) == 0
15559       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15560     (*p) += len + 2;
15561     sscanf(*p, "%[^\"]", loc);
15562     while (**p && **p != '\"') (*p)++;
15563     if (**p == '\"') (*p)++;
15564     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15565     SendToProgram(buf, cps);
15566     return TRUE;
15567   }
15568   return FALSE;
15569 }
15570
15571 int
15572 ParseOption (Option *opt, ChessProgramState *cps)
15573 // [HGM] options: process the string that defines an engine option, and determine
15574 // name, type, default value, and allowed value range
15575 {
15576         char *p, *q, buf[MSG_SIZ];
15577         int n, min = (-1)<<31, max = 1<<31, def;
15578
15579         if(p = strstr(opt->name, " -spin ")) {
15580             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15581             if(max < min) max = min; // enforce consistency
15582             if(def < min) def = min;
15583             if(def > max) def = max;
15584             opt->value = def;
15585             opt->min = min;
15586             opt->max = max;
15587             opt->type = Spin;
15588         } else if((p = strstr(opt->name, " -slider "))) {
15589             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15590             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15591             if(max < min) max = min; // enforce consistency
15592             if(def < min) def = min;
15593             if(def > max) def = max;
15594             opt->value = def;
15595             opt->min = min;
15596             opt->max = max;
15597             opt->type = Spin; // Slider;
15598         } else if((p = strstr(opt->name, " -string "))) {
15599             opt->textValue = p+9;
15600             opt->type = TextBox;
15601         } else if((p = strstr(opt->name, " -file "))) {
15602             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15603             opt->textValue = p+7;
15604             opt->type = FileName; // FileName;
15605         } else if((p = strstr(opt->name, " -path "))) {
15606             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15607             opt->textValue = p+7;
15608             opt->type = PathName; // PathName;
15609         } else if(p = strstr(opt->name, " -check ")) {
15610             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15611             opt->value = (def != 0);
15612             opt->type = CheckBox;
15613         } else if(p = strstr(opt->name, " -combo ")) {
15614             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15615             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15616             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15617             opt->value = n = 0;
15618             while(q = StrStr(q, " /// ")) {
15619                 n++; *q = 0;    // count choices, and null-terminate each of them
15620                 q += 5;
15621                 if(*q == '*') { // remember default, which is marked with * prefix
15622                     q++;
15623                     opt->value = n;
15624                 }
15625                 cps->comboList[cps->comboCnt++] = q;
15626             }
15627             cps->comboList[cps->comboCnt++] = NULL;
15628             opt->max = n + 1;
15629             opt->type = ComboBox;
15630         } else if(p = strstr(opt->name, " -button")) {
15631             opt->type = Button;
15632         } else if(p = strstr(opt->name, " -save")) {
15633             opt->type = SaveButton;
15634         } else return FALSE;
15635         *p = 0; // terminate option name
15636         // now look if the command-line options define a setting for this engine option.
15637         if(cps->optionSettings && cps->optionSettings[0])
15638             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15639         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15640           snprintf(buf, MSG_SIZ, "option %s", p);
15641                 if(p = strstr(buf, ",")) *p = 0;
15642                 if(q = strchr(buf, '=')) switch(opt->type) {
15643                     case ComboBox:
15644                         for(n=0; n<opt->max; n++)
15645                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15646                         break;
15647                     case TextBox:
15648                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15649                         break;
15650                     case Spin:
15651                     case CheckBox:
15652                         opt->value = atoi(q+1);
15653                     default:
15654                         break;
15655                 }
15656                 strcat(buf, "\n");
15657                 SendToProgram(buf, cps);
15658         }
15659         return TRUE;
15660 }
15661
15662 void
15663 FeatureDone (ChessProgramState *cps, int val)
15664 {
15665   DelayedEventCallback cb = GetDelayedEvent();
15666   if ((cb == InitBackEnd3 && cps == &first) ||
15667       (cb == SettingsMenuIfReady && cps == &second) ||
15668       (cb == LoadEngine) ||
15669       (cb == TwoMachinesEventIfReady)) {
15670     CancelDelayedEvent();
15671     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15672   }
15673   cps->initDone = val;
15674 }
15675
15676 /* Parse feature command from engine */
15677 void
15678 ParseFeatures (char *args, ChessProgramState *cps)
15679 {
15680   char *p = args;
15681   char *q;
15682   int val;
15683   char buf[MSG_SIZ];
15684
15685   for (;;) {
15686     while (*p == ' ') p++;
15687     if (*p == NULLCHAR) return;
15688
15689     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15690     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15691     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15692     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15693     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15694     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15695     if (BoolFeature(&p, "reuse", &val, cps)) {
15696       /* Engine can disable reuse, but can't enable it if user said no */
15697       if (!val) cps->reuse = FALSE;
15698       continue;
15699     }
15700     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15701     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15702       if (gameMode == TwoMachinesPlay) {
15703         DisplayTwoMachinesTitle();
15704       } else {
15705         DisplayTitle("");
15706       }
15707       continue;
15708     }
15709     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15710     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15711     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15712     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15713     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15714     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15715     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15716     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15717     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15718     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15719     if (IntFeature(&p, "done", &val, cps)) {
15720       FeatureDone(cps, val);
15721       continue;
15722     }
15723     /* Added by Tord: */
15724     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15725     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15726     /* End of additions by Tord */
15727
15728     /* [HGM] added features: */
15729     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15730     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15731     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15732     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15733     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15734     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15735     if (StringFeature(&p, "option", buf, cps)) {
15736         FREE(cps->option[cps->nrOptions].name);
15737         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15738         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15739         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15740           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15741             SendToProgram(buf, cps);
15742             continue;
15743         }
15744         if(cps->nrOptions >= MAX_OPTIONS) {
15745             cps->nrOptions--;
15746             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15747             DisplayError(buf, 0);
15748         }
15749         continue;
15750     }
15751     /* End of additions by HGM */
15752
15753     /* unknown feature: complain and skip */
15754     q = p;
15755     while (*q && *q != '=') q++;
15756     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15757     SendToProgram(buf, cps);
15758     p = q;
15759     if (*p == '=') {
15760       p++;
15761       if (*p == '\"') {
15762         p++;
15763         while (*p && *p != '\"') p++;
15764         if (*p == '\"') p++;
15765       } else {
15766         while (*p && *p != ' ') p++;
15767       }
15768     }
15769   }
15770
15771 }
15772
15773 void
15774 PeriodicUpdatesEvent (int newState)
15775 {
15776     if (newState == appData.periodicUpdates)
15777       return;
15778
15779     appData.periodicUpdates=newState;
15780
15781     /* Display type changes, so update it now */
15782 //    DisplayAnalysis();
15783
15784     /* Get the ball rolling again... */
15785     if (newState) {
15786         AnalysisPeriodicEvent(1);
15787         StartAnalysisClock();
15788     }
15789 }
15790
15791 void
15792 PonderNextMoveEvent (int newState)
15793 {
15794     if (newState == appData.ponderNextMove) return;
15795     if (gameMode == EditPosition) EditPositionDone(TRUE);
15796     if (newState) {
15797         SendToProgram("hard\n", &first);
15798         if (gameMode == TwoMachinesPlay) {
15799             SendToProgram("hard\n", &second);
15800         }
15801     } else {
15802         SendToProgram("easy\n", &first);
15803         thinkOutput[0] = NULLCHAR;
15804         if (gameMode == TwoMachinesPlay) {
15805             SendToProgram("easy\n", &second);
15806         }
15807     }
15808     appData.ponderNextMove = newState;
15809 }
15810
15811 void
15812 NewSettingEvent (int option, int *feature, char *command, int value)
15813 {
15814     char buf[MSG_SIZ];
15815
15816     if (gameMode == EditPosition) EditPositionDone(TRUE);
15817     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15818     if(feature == NULL || *feature) SendToProgram(buf, &first);
15819     if (gameMode == TwoMachinesPlay) {
15820         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15821     }
15822 }
15823
15824 void
15825 ShowThinkingEvent ()
15826 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15827 {
15828     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15829     int newState = appData.showThinking
15830         // [HGM] thinking: other features now need thinking output as well
15831         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15832
15833     if (oldState == newState) return;
15834     oldState = newState;
15835     if (gameMode == EditPosition) EditPositionDone(TRUE);
15836     if (oldState) {
15837         SendToProgram("post\n", &first);
15838         if (gameMode == TwoMachinesPlay) {
15839             SendToProgram("post\n", &second);
15840         }
15841     } else {
15842         SendToProgram("nopost\n", &first);
15843         thinkOutput[0] = NULLCHAR;
15844         if (gameMode == TwoMachinesPlay) {
15845             SendToProgram("nopost\n", &second);
15846         }
15847     }
15848 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15849 }
15850
15851 void
15852 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15853 {
15854   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15855   if (pr == NoProc) return;
15856   AskQuestion(title, question, replyPrefix, pr);
15857 }
15858
15859 void
15860 TypeInEvent (char firstChar)
15861 {
15862     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15863         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15864         gameMode == AnalyzeMode || gameMode == EditGame || 
15865         gameMode == EditPosition || gameMode == IcsExamining ||
15866         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15867         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15868                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15869                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15870         gameMode == Training) PopUpMoveDialog(firstChar);
15871 }
15872
15873 void
15874 TypeInDoneEvent (char *move)
15875 {
15876         Board board;
15877         int n, fromX, fromY, toX, toY;
15878         char promoChar;
15879         ChessMove moveType;
15880
15881         // [HGM] FENedit
15882         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15883                 EditPositionPasteFEN(move);
15884                 return;
15885         }
15886         // [HGM] movenum: allow move number to be typed in any mode
15887         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15888           ToNrEvent(2*n-1);
15889           return;
15890         }
15891         // undocumented kludge: allow command-line option to be typed in!
15892         // (potentially fatal, and does not implement the effect of the option.)
15893         // should only be used for options that are values on which future decisions will be made,
15894         // and definitely not on options that would be used during initialization.
15895         if(strstr(move, "!!! -") == move) {
15896             ParseArgsFromString(move+4);
15897             return;
15898         }
15899
15900       if (gameMode != EditGame && currentMove != forwardMostMove && 
15901         gameMode != Training) {
15902         DisplayMoveError(_("Displayed move is not current"));
15903       } else {
15904         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15905           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15906         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15907         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15908           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15909           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15910         } else {
15911           DisplayMoveError(_("Could not parse move"));
15912         }
15913       }
15914 }
15915
15916 void
15917 DisplayMove (int moveNumber)
15918 {
15919     char message[MSG_SIZ];
15920     char res[MSG_SIZ];
15921     char cpThinkOutput[MSG_SIZ];
15922
15923     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15924
15925     if (moveNumber == forwardMostMove - 1 ||
15926         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15927
15928         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15929
15930         if (strchr(cpThinkOutput, '\n')) {
15931             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15932         }
15933     } else {
15934         *cpThinkOutput = NULLCHAR;
15935     }
15936
15937     /* [AS] Hide thinking from human user */
15938     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15939         *cpThinkOutput = NULLCHAR;
15940         if( thinkOutput[0] != NULLCHAR ) {
15941             int i;
15942
15943             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15944                 cpThinkOutput[i] = '.';
15945             }
15946             cpThinkOutput[i] = NULLCHAR;
15947             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15948         }
15949     }
15950
15951     if (moveNumber == forwardMostMove - 1 &&
15952         gameInfo.resultDetails != NULL) {
15953         if (gameInfo.resultDetails[0] == NULLCHAR) {
15954           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15955         } else {
15956           snprintf(res, MSG_SIZ, " {%s} %s",
15957                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15958         }
15959     } else {
15960         res[0] = NULLCHAR;
15961     }
15962
15963     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15964         DisplayMessage(res, cpThinkOutput);
15965     } else {
15966       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15967                 WhiteOnMove(moveNumber) ? " " : ".. ",
15968                 parseList[moveNumber], res);
15969         DisplayMessage(message, cpThinkOutput);
15970     }
15971 }
15972
15973 void
15974 DisplayComment (int moveNumber, char *text)
15975 {
15976     char title[MSG_SIZ];
15977
15978     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15979       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15980     } else {
15981       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15982               WhiteOnMove(moveNumber) ? " " : ".. ",
15983               parseList[moveNumber]);
15984     }
15985     if (text != NULL && (appData.autoDisplayComment || commentUp))
15986         CommentPopUp(title, text);
15987 }
15988
15989 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15990  * might be busy thinking or pondering.  It can be omitted if your
15991  * gnuchess is configured to stop thinking immediately on any user
15992  * input.  However, that gnuchess feature depends on the FIONREAD
15993  * ioctl, which does not work properly on some flavors of Unix.
15994  */
15995 void
15996 Attention (ChessProgramState *cps)
15997 {
15998 #if ATTENTION
15999     if (!cps->useSigint) return;
16000     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16001     switch (gameMode) {
16002       case MachinePlaysWhite:
16003       case MachinePlaysBlack:
16004       case TwoMachinesPlay:
16005       case IcsPlayingWhite:
16006       case IcsPlayingBlack:
16007       case AnalyzeMode:
16008       case AnalyzeFile:
16009         /* Skip if we know it isn't thinking */
16010         if (!cps->maybeThinking) return;
16011         if (appData.debugMode)
16012           fprintf(debugFP, "Interrupting %s\n", cps->which);
16013         InterruptChildProcess(cps->pr);
16014         cps->maybeThinking = FALSE;
16015         break;
16016       default:
16017         break;
16018     }
16019 #endif /*ATTENTION*/
16020 }
16021
16022 int
16023 CheckFlags ()
16024 {
16025     if (whiteTimeRemaining <= 0) {
16026         if (!whiteFlag) {
16027             whiteFlag = TRUE;
16028             if (appData.icsActive) {
16029                 if (appData.autoCallFlag &&
16030                     gameMode == IcsPlayingBlack && !blackFlag) {
16031                   SendToICS(ics_prefix);
16032                   SendToICS("flag\n");
16033                 }
16034             } else {
16035                 if (blackFlag) {
16036                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16037                 } else {
16038                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16039                     if (appData.autoCallFlag) {
16040                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16041                         return TRUE;
16042                     }
16043                 }
16044             }
16045         }
16046     }
16047     if (blackTimeRemaining <= 0) {
16048         if (!blackFlag) {
16049             blackFlag = TRUE;
16050             if (appData.icsActive) {
16051                 if (appData.autoCallFlag &&
16052                     gameMode == IcsPlayingWhite && !whiteFlag) {
16053                   SendToICS(ics_prefix);
16054                   SendToICS("flag\n");
16055                 }
16056             } else {
16057                 if (whiteFlag) {
16058                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16059                 } else {
16060                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16061                     if (appData.autoCallFlag) {
16062                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16063                         return TRUE;
16064                     }
16065                 }
16066             }
16067         }
16068     }
16069     return FALSE;
16070 }
16071
16072 void
16073 CheckTimeControl ()
16074 {
16075     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16076         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16077
16078     /*
16079      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16080      */
16081     if ( !WhiteOnMove(forwardMostMove) ) {
16082         /* White made time control */
16083         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16084         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16085         /* [HGM] time odds: correct new time quota for time odds! */
16086                                             / WhitePlayer()->timeOdds;
16087         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16088     } else {
16089         lastBlack -= blackTimeRemaining;
16090         /* Black made time control */
16091         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16092                                             / WhitePlayer()->other->timeOdds;
16093         lastWhite = whiteTimeRemaining;
16094     }
16095 }
16096
16097 void
16098 DisplayBothClocks ()
16099 {
16100     int wom = gameMode == EditPosition ?
16101       !blackPlaysFirst : WhiteOnMove(currentMove);
16102     DisplayWhiteClock(whiteTimeRemaining, wom);
16103     DisplayBlackClock(blackTimeRemaining, !wom);
16104 }
16105
16106
16107 /* Timekeeping seems to be a portability nightmare.  I think everyone
16108    has ftime(), but I'm really not sure, so I'm including some ifdefs
16109    to use other calls if you don't.  Clocks will be less accurate if
16110    you have neither ftime nor gettimeofday.
16111 */
16112
16113 /* VS 2008 requires the #include outside of the function */
16114 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16115 #include <sys/timeb.h>
16116 #endif
16117
16118 /* Get the current time as a TimeMark */
16119 void
16120 GetTimeMark (TimeMark *tm)
16121 {
16122 #if HAVE_GETTIMEOFDAY
16123
16124     struct timeval timeVal;
16125     struct timezone timeZone;
16126
16127     gettimeofday(&timeVal, &timeZone);
16128     tm->sec = (long) timeVal.tv_sec;
16129     tm->ms = (int) (timeVal.tv_usec / 1000L);
16130
16131 #else /*!HAVE_GETTIMEOFDAY*/
16132 #if HAVE_FTIME
16133
16134 // include <sys/timeb.h> / moved to just above start of function
16135     struct timeb timeB;
16136
16137     ftime(&timeB);
16138     tm->sec = (long) timeB.time;
16139     tm->ms = (int) timeB.millitm;
16140
16141 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16142     tm->sec = (long) time(NULL);
16143     tm->ms = 0;
16144 #endif
16145 #endif
16146 }
16147
16148 /* Return the difference in milliseconds between two
16149    time marks.  We assume the difference will fit in a long!
16150 */
16151 long
16152 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16153 {
16154     return 1000L*(tm2->sec - tm1->sec) +
16155            (long) (tm2->ms - tm1->ms);
16156 }
16157
16158
16159 /*
16160  * Code to manage the game clocks.
16161  *
16162  * In tournament play, black starts the clock and then white makes a move.
16163  * We give the human user a slight advantage if he is playing white---the
16164  * clocks don't run until he makes his first move, so it takes zero time.
16165  * Also, we don't account for network lag, so we could get out of sync
16166  * with GNU Chess's clock -- but then, referees are always right.
16167  */
16168
16169 static TimeMark tickStartTM;
16170 static long intendedTickLength;
16171
16172 long
16173 NextTickLength (long timeRemaining)
16174 {
16175     long nominalTickLength, nextTickLength;
16176
16177     if (timeRemaining > 0L && timeRemaining <= 10000L)
16178       nominalTickLength = 100L;
16179     else
16180       nominalTickLength = 1000L;
16181     nextTickLength = timeRemaining % nominalTickLength;
16182     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16183
16184     return nextTickLength;
16185 }
16186
16187 /* Adjust clock one minute up or down */
16188 void
16189 AdjustClock (Boolean which, int dir)
16190 {
16191     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16192     if(which) blackTimeRemaining += 60000*dir;
16193     else      whiteTimeRemaining += 60000*dir;
16194     DisplayBothClocks();
16195     adjustedClock = TRUE;
16196 }
16197
16198 /* Stop clocks and reset to a fresh time control */
16199 void
16200 ResetClocks ()
16201 {
16202     (void) StopClockTimer();
16203     if (appData.icsActive) {
16204         whiteTimeRemaining = blackTimeRemaining = 0;
16205     } else if (searchTime) {
16206         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16207         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16208     } else { /* [HGM] correct new time quote for time odds */
16209         whiteTC = blackTC = fullTimeControlString;
16210         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16211         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16212     }
16213     if (whiteFlag || blackFlag) {
16214         DisplayTitle("");
16215         whiteFlag = blackFlag = FALSE;
16216     }
16217     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16218     DisplayBothClocks();
16219     adjustedClock = FALSE;
16220 }
16221
16222 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16223
16224 /* Decrement running clock by amount of time that has passed */
16225 void
16226 DecrementClocks ()
16227 {
16228     long timeRemaining;
16229     long lastTickLength, fudge;
16230     TimeMark now;
16231
16232     if (!appData.clockMode) return;
16233     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16234
16235     GetTimeMark(&now);
16236
16237     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16238
16239     /* Fudge if we woke up a little too soon */
16240     fudge = intendedTickLength - lastTickLength;
16241     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16242
16243     if (WhiteOnMove(forwardMostMove)) {
16244         if(whiteNPS >= 0) lastTickLength = 0;
16245         timeRemaining = whiteTimeRemaining -= lastTickLength;
16246         if(timeRemaining < 0 && !appData.icsActive) {
16247             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16248             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16249                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16250                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16251             }
16252         }
16253         DisplayWhiteClock(whiteTimeRemaining - fudge,
16254                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16255     } else {
16256         if(blackNPS >= 0) lastTickLength = 0;
16257         timeRemaining = blackTimeRemaining -= lastTickLength;
16258         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16259             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16260             if(suddenDeath) {
16261                 blackStartMove = forwardMostMove;
16262                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16263             }
16264         }
16265         DisplayBlackClock(blackTimeRemaining - fudge,
16266                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16267     }
16268     if (CheckFlags()) return;
16269
16270     if(twoBoards) { // count down secondary board's clocks as well
16271         activePartnerTime -= lastTickLength;
16272         partnerUp = 1;
16273         if(activePartner == 'W')
16274             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16275         else
16276             DisplayBlackClock(activePartnerTime, TRUE);
16277         partnerUp = 0;
16278     }
16279
16280     tickStartTM = now;
16281     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16282     StartClockTimer(intendedTickLength);
16283
16284     /* if the time remaining has fallen below the alarm threshold, sound the
16285      * alarm. if the alarm has sounded and (due to a takeback or time control
16286      * with increment) the time remaining has increased to a level above the
16287      * threshold, reset the alarm so it can sound again.
16288      */
16289
16290     if (appData.icsActive && appData.icsAlarm) {
16291
16292         /* make sure we are dealing with the user's clock */
16293         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16294                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16295            )) return;
16296
16297         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16298             alarmSounded = FALSE;
16299         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16300             PlayAlarmSound();
16301             alarmSounded = TRUE;
16302         }
16303     }
16304 }
16305
16306
16307 /* A player has just moved, so stop the previously running
16308    clock and (if in clock mode) start the other one.
16309    We redisplay both clocks in case we're in ICS mode, because
16310    ICS gives us an update to both clocks after every move.
16311    Note that this routine is called *after* forwardMostMove
16312    is updated, so the last fractional tick must be subtracted
16313    from the color that is *not* on move now.
16314 */
16315 void
16316 SwitchClocks (int newMoveNr)
16317 {
16318     long lastTickLength;
16319     TimeMark now;
16320     int flagged = FALSE;
16321
16322     GetTimeMark(&now);
16323
16324     if (StopClockTimer() && appData.clockMode) {
16325         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16326         if (!WhiteOnMove(forwardMostMove)) {
16327             if(blackNPS >= 0) lastTickLength = 0;
16328             blackTimeRemaining -= lastTickLength;
16329            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16330 //         if(pvInfoList[forwardMostMove].time == -1)
16331                  pvInfoList[forwardMostMove].time =               // use GUI time
16332                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16333         } else {
16334            if(whiteNPS >= 0) lastTickLength = 0;
16335            whiteTimeRemaining -= lastTickLength;
16336            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16337 //         if(pvInfoList[forwardMostMove].time == -1)
16338                  pvInfoList[forwardMostMove].time =
16339                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16340         }
16341         flagged = CheckFlags();
16342     }
16343     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16344     CheckTimeControl();
16345
16346     if (flagged || !appData.clockMode) return;
16347
16348     switch (gameMode) {
16349       case MachinePlaysBlack:
16350       case MachinePlaysWhite:
16351       case BeginningOfGame:
16352         if (pausing) return;
16353         break;
16354
16355       case EditGame:
16356       case PlayFromGameFile:
16357       case IcsExamining:
16358         return;
16359
16360       default:
16361         break;
16362     }
16363
16364     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16365         if(WhiteOnMove(forwardMostMove))
16366              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16367         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16368     }
16369
16370     tickStartTM = now;
16371     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16372       whiteTimeRemaining : blackTimeRemaining);
16373     StartClockTimer(intendedTickLength);
16374 }
16375
16376
16377 /* Stop both clocks */
16378 void
16379 StopClocks ()
16380 {
16381     long lastTickLength;
16382     TimeMark now;
16383
16384     if (!StopClockTimer()) return;
16385     if (!appData.clockMode) return;
16386
16387     GetTimeMark(&now);
16388
16389     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16390     if (WhiteOnMove(forwardMostMove)) {
16391         if(whiteNPS >= 0) lastTickLength = 0;
16392         whiteTimeRemaining -= lastTickLength;
16393         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16394     } else {
16395         if(blackNPS >= 0) lastTickLength = 0;
16396         blackTimeRemaining -= lastTickLength;
16397         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16398     }
16399     CheckFlags();
16400 }
16401
16402 /* Start clock of player on move.  Time may have been reset, so
16403    if clock is already running, stop and restart it. */
16404 void
16405 StartClocks ()
16406 {
16407     (void) StopClockTimer(); /* in case it was running already */
16408     DisplayBothClocks();
16409     if (CheckFlags()) return;
16410
16411     if (!appData.clockMode) return;
16412     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16413
16414     GetTimeMark(&tickStartTM);
16415     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16416       whiteTimeRemaining : blackTimeRemaining);
16417
16418    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16419     whiteNPS = blackNPS = -1;
16420     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16421        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16422         whiteNPS = first.nps;
16423     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16424        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16425         blackNPS = first.nps;
16426     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16427         whiteNPS = second.nps;
16428     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16429         blackNPS = second.nps;
16430     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16431
16432     StartClockTimer(intendedTickLength);
16433 }
16434
16435 char *
16436 TimeString (long ms)
16437 {
16438     long second, minute, hour, day;
16439     char *sign = "";
16440     static char buf[32];
16441
16442     if (ms > 0 && ms <= 9900) {
16443       /* convert milliseconds to tenths, rounding up */
16444       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16445
16446       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16447       return buf;
16448     }
16449
16450     /* convert milliseconds to seconds, rounding up */
16451     /* use floating point to avoid strangeness of integer division
16452        with negative dividends on many machines */
16453     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16454
16455     if (second < 0) {
16456         sign = "-";
16457         second = -second;
16458     }
16459
16460     day = second / (60 * 60 * 24);
16461     second = second % (60 * 60 * 24);
16462     hour = second / (60 * 60);
16463     second = second % (60 * 60);
16464     minute = second / 60;
16465     second = second % 60;
16466
16467     if (day > 0)
16468       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16469               sign, day, hour, minute, second);
16470     else if (hour > 0)
16471       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16472     else
16473       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16474
16475     return buf;
16476 }
16477
16478
16479 /*
16480  * This is necessary because some C libraries aren't ANSI C compliant yet.
16481  */
16482 char *
16483 StrStr (char *string, char *match)
16484 {
16485     int i, length;
16486
16487     length = strlen(match);
16488
16489     for (i = strlen(string) - length; i >= 0; i--, string++)
16490       if (!strncmp(match, string, length))
16491         return string;
16492
16493     return NULL;
16494 }
16495
16496 char *
16497 StrCaseStr (char *string, char *match)
16498 {
16499     int i, j, length;
16500
16501     length = strlen(match);
16502
16503     for (i = strlen(string) - length; i >= 0; i--, string++) {
16504         for (j = 0; j < length; j++) {
16505             if (ToLower(match[j]) != ToLower(string[j]))
16506               break;
16507         }
16508         if (j == length) return string;
16509     }
16510
16511     return NULL;
16512 }
16513
16514 #ifndef _amigados
16515 int
16516 StrCaseCmp (char *s1, char *s2)
16517 {
16518     char c1, c2;
16519
16520     for (;;) {
16521         c1 = ToLower(*s1++);
16522         c2 = ToLower(*s2++);
16523         if (c1 > c2) return 1;
16524         if (c1 < c2) return -1;
16525         if (c1 == NULLCHAR) return 0;
16526     }
16527 }
16528
16529
16530 int
16531 ToLower (int c)
16532 {
16533     return isupper(c) ? tolower(c) : c;
16534 }
16535
16536
16537 int
16538 ToUpper (int c)
16539 {
16540     return islower(c) ? toupper(c) : c;
16541 }
16542 #endif /* !_amigados    */
16543
16544 char *
16545 StrSave (char *s)
16546 {
16547   char *ret;
16548
16549   if ((ret = (char *) malloc(strlen(s) + 1)))
16550     {
16551       safeStrCpy(ret, s, strlen(s)+1);
16552     }
16553   return ret;
16554 }
16555
16556 char *
16557 StrSavePtr (char *s, char **savePtr)
16558 {
16559     if (*savePtr) {
16560         free(*savePtr);
16561     }
16562     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16563       safeStrCpy(*savePtr, s, strlen(s)+1);
16564     }
16565     return(*savePtr);
16566 }
16567
16568 char *
16569 PGNDate ()
16570 {
16571     time_t clock;
16572     struct tm *tm;
16573     char buf[MSG_SIZ];
16574
16575     clock = time((time_t *)NULL);
16576     tm = localtime(&clock);
16577     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16578             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16579     return StrSave(buf);
16580 }
16581
16582
16583 char *
16584 PositionToFEN (int move, char *overrideCastling)
16585 {
16586     int i, j, fromX, fromY, toX, toY;
16587     int whiteToPlay;
16588     char buf[MSG_SIZ];
16589     char *p, *q;
16590     int emptycount;
16591     ChessSquare piece;
16592
16593     whiteToPlay = (gameMode == EditPosition) ?
16594       !blackPlaysFirst : (move % 2 == 0);
16595     p = buf;
16596
16597     /* Piece placement data */
16598     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16599         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16600         emptycount = 0;
16601         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16602             if (boards[move][i][j] == EmptySquare) {
16603                 emptycount++;
16604             } else { ChessSquare piece = boards[move][i][j];
16605                 if (emptycount > 0) {
16606                     if(emptycount<10) /* [HGM] can be >= 10 */
16607                         *p++ = '0' + emptycount;
16608                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16609                     emptycount = 0;
16610                 }
16611                 if(PieceToChar(piece) == '+') {
16612                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16613                     *p++ = '+';
16614                     piece = (ChessSquare)(DEMOTED piece);
16615                 }
16616                 *p++ = PieceToChar(piece);
16617                 if(p[-1] == '~') {
16618                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16619                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16620                     *p++ = '~';
16621                 }
16622             }
16623         }
16624         if (emptycount > 0) {
16625             if(emptycount<10) /* [HGM] can be >= 10 */
16626                 *p++ = '0' + emptycount;
16627             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16628             emptycount = 0;
16629         }
16630         *p++ = '/';
16631     }
16632     *(p - 1) = ' ';
16633
16634     /* [HGM] print Crazyhouse or Shogi holdings */
16635     if( gameInfo.holdingsWidth ) {
16636         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16637         q = p;
16638         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16639             piece = boards[move][i][BOARD_WIDTH-1];
16640             if( piece != EmptySquare )
16641               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16642                   *p++ = PieceToChar(piece);
16643         }
16644         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16645             piece = boards[move][BOARD_HEIGHT-i-1][0];
16646             if( piece != EmptySquare )
16647               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16648                   *p++ = PieceToChar(piece);
16649         }
16650
16651         if( q == p ) *p++ = '-';
16652         *p++ = ']';
16653         *p++ = ' ';
16654     }
16655
16656     /* Active color */
16657     *p++ = whiteToPlay ? 'w' : 'b';
16658     *p++ = ' ';
16659
16660   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16661     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16662   } else {
16663   if(nrCastlingRights) {
16664      q = p;
16665      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16666        /* [HGM] write directly from rights */
16667            if(boards[move][CASTLING][2] != NoRights &&
16668               boards[move][CASTLING][0] != NoRights   )
16669                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16670            if(boards[move][CASTLING][2] != NoRights &&
16671               boards[move][CASTLING][1] != NoRights   )
16672                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16673            if(boards[move][CASTLING][5] != NoRights &&
16674               boards[move][CASTLING][3] != NoRights   )
16675                 *p++ = boards[move][CASTLING][3] + AAA;
16676            if(boards[move][CASTLING][5] != NoRights &&
16677               boards[move][CASTLING][4] != NoRights   )
16678                 *p++ = boards[move][CASTLING][4] + AAA;
16679      } else {
16680
16681         /* [HGM] write true castling rights */
16682         if( nrCastlingRights == 6 ) {
16683             int q, k=0;
16684             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16685                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16686             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16687                  boards[move][CASTLING][2] != NoRights  );
16688             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16689                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16690                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16691                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16692                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16693             }
16694             if(q) *p++ = 'Q';
16695             k = 0;
16696             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16697                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16698             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16699                  boards[move][CASTLING][5] != NoRights  );
16700             if(gameInfo.variant == VariantSChess) {
16701                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16702                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16703                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16704                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16705             }
16706             if(q) *p++ = 'q';
16707         }
16708      }
16709      if (q == p) *p++ = '-'; /* No castling rights */
16710      *p++ = ' ';
16711   }
16712
16713   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16714      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16715     /* En passant target square */
16716     if (move > backwardMostMove) {
16717         fromX = moveList[move - 1][0] - AAA;
16718         fromY = moveList[move - 1][1] - ONE;
16719         toX = moveList[move - 1][2] - AAA;
16720         toY = moveList[move - 1][3] - ONE;
16721         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16722             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16723             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16724             fromX == toX) {
16725             /* 2-square pawn move just happened */
16726             *p++ = toX + AAA;
16727             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16728         } else {
16729             *p++ = '-';
16730         }
16731     } else if(move == backwardMostMove) {
16732         // [HGM] perhaps we should always do it like this, and forget the above?
16733         if((signed char)boards[move][EP_STATUS] >= 0) {
16734             *p++ = boards[move][EP_STATUS] + AAA;
16735             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16736         } else {
16737             *p++ = '-';
16738         }
16739     } else {
16740         *p++ = '-';
16741     }
16742     *p++ = ' ';
16743   }
16744   }
16745
16746     /* [HGM] find reversible plies */
16747     {   int i = 0, j=move;
16748
16749         if (appData.debugMode) { int k;
16750             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16751             for(k=backwardMostMove; k<=forwardMostMove; k++)
16752                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16753
16754         }
16755
16756         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16757         if( j == backwardMostMove ) i += initialRulePlies;
16758         sprintf(p, "%d ", i);
16759         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16760     }
16761     /* Fullmove number */
16762     sprintf(p, "%d", (move / 2) + 1);
16763
16764     return StrSave(buf);
16765 }
16766
16767 Boolean
16768 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16769 {
16770     int i, j;
16771     char *p, c;
16772     int emptycount, virgin[BOARD_FILES];
16773     ChessSquare piece;
16774
16775     p = fen;
16776
16777     /* [HGM] by default clear Crazyhouse holdings, if present */
16778     if(gameInfo.holdingsWidth) {
16779        for(i=0; i<BOARD_HEIGHT; i++) {
16780            board[i][0]             = EmptySquare; /* black holdings */
16781            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16782            board[i][1]             = (ChessSquare) 0; /* black counts */
16783            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16784        }
16785     }
16786
16787     /* Piece placement data */
16788     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16789         j = 0;
16790         for (;;) {
16791             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16792                 if (*p == '/') p++;
16793                 emptycount = gameInfo.boardWidth - j;
16794                 while (emptycount--)
16795                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16796                 break;
16797 #if(BOARD_FILES >= 10)
16798             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16799                 p++; emptycount=10;
16800                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16801                 while (emptycount--)
16802                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16803 #endif
16804             } else if (isdigit(*p)) {
16805                 emptycount = *p++ - '0';
16806                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16807                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16808                 while (emptycount--)
16809                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16810             } else if (*p == '+' || isalpha(*p)) {
16811                 if (j >= gameInfo.boardWidth) return FALSE;
16812                 if(*p=='+') {
16813                     piece = CharToPiece(*++p);
16814                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16815                     piece = (ChessSquare) (PROMOTED piece ); p++;
16816                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16817                 } else piece = CharToPiece(*p++);
16818
16819                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16820                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16821                     piece = (ChessSquare) (PROMOTED piece);
16822                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16823                     p++;
16824                 }
16825                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16826             } else {
16827                 return FALSE;
16828             }
16829         }
16830     }
16831     while (*p == '/' || *p == ' ') p++;
16832
16833     /* [HGM] look for Crazyhouse holdings here */
16834     while(*p==' ') p++;
16835     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16836         if(*p == '[') p++;
16837         if(*p == '-' ) p++; /* empty holdings */ else {
16838             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16839             /* if we would allow FEN reading to set board size, we would   */
16840             /* have to add holdings and shift the board read so far here   */
16841             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16842                 p++;
16843                 if((int) piece >= (int) BlackPawn ) {
16844                     i = (int)piece - (int)BlackPawn;
16845                     i = PieceToNumber((ChessSquare)i);
16846                     if( i >= gameInfo.holdingsSize ) return FALSE;
16847                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16848                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16849                 } else {
16850                     i = (int)piece - (int)WhitePawn;
16851                     i = PieceToNumber((ChessSquare)i);
16852                     if( i >= gameInfo.holdingsSize ) return FALSE;
16853                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16854                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16855                 }
16856             }
16857         }
16858         if(*p == ']') p++;
16859     }
16860
16861     while(*p == ' ') p++;
16862
16863     /* Active color */
16864     c = *p++;
16865     if(appData.colorNickNames) {
16866       if( c == appData.colorNickNames[0] ) c = 'w'; else
16867       if( c == appData.colorNickNames[1] ) c = 'b';
16868     }
16869     switch (c) {
16870       case 'w':
16871         *blackPlaysFirst = FALSE;
16872         break;
16873       case 'b':
16874         *blackPlaysFirst = TRUE;
16875         break;
16876       default:
16877         return FALSE;
16878     }
16879
16880     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16881     /* return the extra info in global variiables             */
16882
16883     /* set defaults in case FEN is incomplete */
16884     board[EP_STATUS] = EP_UNKNOWN;
16885     for(i=0; i<nrCastlingRights; i++ ) {
16886         board[CASTLING][i] =
16887             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16888     }   /* assume possible unless obviously impossible */
16889     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16890     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16891     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16892                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16893     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16894     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16895     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16896                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16897     FENrulePlies = 0;
16898
16899     while(*p==' ') p++;
16900     if(nrCastlingRights) {
16901       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
16902       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
16903           /* castling indicator present, so default becomes no castlings */
16904           for(i=0; i<nrCastlingRights; i++ ) {
16905                  board[CASTLING][i] = NoRights;
16906           }
16907       }
16908       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16909              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
16910              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16911              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16912         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
16913
16914         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16915             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16916             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16917         }
16918         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16919             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16920         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16921                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16922         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16923                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16924         switch(c) {
16925           case'K':
16926               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16927               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16928               board[CASTLING][2] = whiteKingFile;
16929               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
16930               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16931               break;
16932           case'Q':
16933               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16934               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16935               board[CASTLING][2] = whiteKingFile;
16936               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
16937               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
16938               break;
16939           case'k':
16940               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16941               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16942               board[CASTLING][5] = blackKingFile;
16943               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
16944               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
16945               break;
16946           case'q':
16947               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16948               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16949               board[CASTLING][5] = blackKingFile;
16950               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
16951               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
16952           case '-':
16953               break;
16954           default: /* FRC castlings */
16955               if(c >= 'a') { /* black rights */
16956                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
16957                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16958                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16959                   if(i == BOARD_RGHT) break;
16960                   board[CASTLING][5] = i;
16961                   c -= AAA;
16962                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16963                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16964                   if(c > i)
16965                       board[CASTLING][3] = c;
16966                   else
16967                       board[CASTLING][4] = c;
16968               } else { /* white rights */
16969                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
16970                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16971                     if(board[0][i] == WhiteKing) break;
16972                   if(i == BOARD_RGHT) break;
16973                   board[CASTLING][2] = i;
16974                   c -= AAA - 'a' + 'A';
16975                   if(board[0][c] >= WhiteKing) break;
16976                   if(c > i)
16977                       board[CASTLING][0] = c;
16978                   else
16979                       board[CASTLING][1] = c;
16980               }
16981         }
16982       }
16983       for(i=0; i<nrCastlingRights; i++)
16984         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16985       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
16986     if (appData.debugMode) {
16987         fprintf(debugFP, "FEN castling rights:");
16988         for(i=0; i<nrCastlingRights; i++)
16989         fprintf(debugFP, " %d", board[CASTLING][i]);
16990         fprintf(debugFP, "\n");
16991     }
16992
16993       while(*p==' ') p++;
16994     }
16995
16996     /* read e.p. field in games that know e.p. capture */
16997     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16998        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16999       if(*p=='-') {
17000         p++; board[EP_STATUS] = EP_NONE;
17001       } else {
17002          char c = *p++ - AAA;
17003
17004          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17005          if(*p >= '0' && *p <='9') p++;
17006          board[EP_STATUS] = c;
17007       }
17008     }
17009
17010
17011     if(sscanf(p, "%d", &i) == 1) {
17012         FENrulePlies = i; /* 50-move ply counter */
17013         /* (The move number is still ignored)    */
17014     }
17015
17016     return TRUE;
17017 }
17018
17019 void
17020 EditPositionPasteFEN (char *fen)
17021 {
17022   if (fen != NULL) {
17023     Board initial_position;
17024
17025     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17026       DisplayError(_("Bad FEN position in clipboard"), 0);
17027       return ;
17028     } else {
17029       int savedBlackPlaysFirst = blackPlaysFirst;
17030       EditPositionEvent();
17031       blackPlaysFirst = savedBlackPlaysFirst;
17032       CopyBoard(boards[0], initial_position);
17033       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17034       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17035       DisplayBothClocks();
17036       DrawPosition(FALSE, boards[currentMove]);
17037     }
17038   }
17039 }
17040
17041 static char cseq[12] = "\\   ";
17042
17043 Boolean
17044 set_cont_sequence (char *new_seq)
17045 {
17046     int len;
17047     Boolean ret;
17048
17049     // handle bad attempts to set the sequence
17050         if (!new_seq)
17051                 return 0; // acceptable error - no debug
17052
17053     len = strlen(new_seq);
17054     ret = (len > 0) && (len < sizeof(cseq));
17055     if (ret)
17056       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17057     else if (appData.debugMode)
17058       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17059     return ret;
17060 }
17061
17062 /*
17063     reformat a source message so words don't cross the width boundary.  internal
17064     newlines are not removed.  returns the wrapped size (no null character unless
17065     included in source message).  If dest is NULL, only calculate the size required
17066     for the dest buffer.  lp argument indicats line position upon entry, and it's
17067     passed back upon exit.
17068 */
17069 int
17070 wrap (char *dest, char *src, int count, int width, int *lp)
17071 {
17072     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17073
17074     cseq_len = strlen(cseq);
17075     old_line = line = *lp;
17076     ansi = len = clen = 0;
17077
17078     for (i=0; i < count; i++)
17079     {
17080         if (src[i] == '\033')
17081             ansi = 1;
17082
17083         // if we hit the width, back up
17084         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17085         {
17086             // store i & len in case the word is too long
17087             old_i = i, old_len = len;
17088
17089             // find the end of the last word
17090             while (i && src[i] != ' ' && src[i] != '\n')
17091             {
17092                 i--;
17093                 len--;
17094             }
17095
17096             // word too long?  restore i & len before splitting it
17097             if ((old_i-i+clen) >= width)
17098             {
17099                 i = old_i;
17100                 len = old_len;
17101             }
17102
17103             // extra space?
17104             if (i && src[i-1] == ' ')
17105                 len--;
17106
17107             if (src[i] != ' ' && src[i] != '\n')
17108             {
17109                 i--;
17110                 if (len)
17111                     len--;
17112             }
17113
17114             // now append the newline and continuation sequence
17115             if (dest)
17116                 dest[len] = '\n';
17117             len++;
17118             if (dest)
17119                 strncpy(dest+len, cseq, cseq_len);
17120             len += cseq_len;
17121             line = cseq_len;
17122             clen = cseq_len;
17123             continue;
17124         }
17125
17126         if (dest)
17127             dest[len] = src[i];
17128         len++;
17129         if (!ansi)
17130             line++;
17131         if (src[i] == '\n')
17132             line = 0;
17133         if (src[i] == 'm')
17134             ansi = 0;
17135     }
17136     if (dest && appData.debugMode)
17137     {
17138         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17139             count, width, line, len, *lp);
17140         show_bytes(debugFP, src, count);
17141         fprintf(debugFP, "\ndest: ");
17142         show_bytes(debugFP, dest, len);
17143         fprintf(debugFP, "\n");
17144     }
17145     *lp = dest ? line : old_line;
17146
17147     return len;
17148 }
17149
17150 // [HGM] vari: routines for shelving variations
17151 Boolean modeRestore = FALSE;
17152
17153 void
17154 PushInner (int firstMove, int lastMove)
17155 {
17156         int i, j, nrMoves = lastMove - firstMove;
17157
17158         // push current tail of game on stack
17159         savedResult[storedGames] = gameInfo.result;
17160         savedDetails[storedGames] = gameInfo.resultDetails;
17161         gameInfo.resultDetails = NULL;
17162         savedFirst[storedGames] = firstMove;
17163         savedLast [storedGames] = lastMove;
17164         savedFramePtr[storedGames] = framePtr;
17165         framePtr -= nrMoves; // reserve space for the boards
17166         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17167             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17168             for(j=0; j<MOVE_LEN; j++)
17169                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17170             for(j=0; j<2*MOVE_LEN; j++)
17171                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17172             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17173             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17174             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17175             pvInfoList[firstMove+i-1].depth = 0;
17176             commentList[framePtr+i] = commentList[firstMove+i];
17177             commentList[firstMove+i] = NULL;
17178         }
17179
17180         storedGames++;
17181         forwardMostMove = firstMove; // truncate game so we can start variation
17182 }
17183
17184 void
17185 PushTail (int firstMove, int lastMove)
17186 {
17187         if(appData.icsActive) { // only in local mode
17188                 forwardMostMove = currentMove; // mimic old ICS behavior
17189                 return;
17190         }
17191         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17192
17193         PushInner(firstMove, lastMove);
17194         if(storedGames == 1) GreyRevert(FALSE);
17195         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17196 }
17197
17198 void
17199 PopInner (Boolean annotate)
17200 {
17201         int i, j, nrMoves;
17202         char buf[8000], moveBuf[20];
17203
17204         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17205         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17206         nrMoves = savedLast[storedGames] - currentMove;
17207         if(annotate) {
17208                 int cnt = 10;
17209                 if(!WhiteOnMove(currentMove))
17210                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17211                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17212                 for(i=currentMove; i<forwardMostMove; i++) {
17213                         if(WhiteOnMove(i))
17214                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17215                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17216                         strcat(buf, moveBuf);
17217                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17218                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17219                 }
17220                 strcat(buf, ")");
17221         }
17222         for(i=1; i<=nrMoves; i++) { // copy last variation back
17223             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17224             for(j=0; j<MOVE_LEN; j++)
17225                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17226             for(j=0; j<2*MOVE_LEN; j++)
17227                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17228             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17229             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17230             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17231             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17232             commentList[currentMove+i] = commentList[framePtr+i];
17233             commentList[framePtr+i] = NULL;
17234         }
17235         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17236         framePtr = savedFramePtr[storedGames];
17237         gameInfo.result = savedResult[storedGames];
17238         if(gameInfo.resultDetails != NULL) {
17239             free(gameInfo.resultDetails);
17240       }
17241         gameInfo.resultDetails = savedDetails[storedGames];
17242         forwardMostMove = currentMove + nrMoves;
17243 }
17244
17245 Boolean
17246 PopTail (Boolean annotate)
17247 {
17248         if(appData.icsActive) return FALSE; // only in local mode
17249         if(!storedGames) return FALSE; // sanity
17250         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17251
17252         PopInner(annotate);
17253         if(currentMove < forwardMostMove) ForwardEvent(); else
17254         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17255
17256         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17257         return TRUE;
17258 }
17259
17260 void
17261 CleanupTail ()
17262 {       // remove all shelved variations
17263         int i;
17264         for(i=0; i<storedGames; i++) {
17265             if(savedDetails[i])
17266                 free(savedDetails[i]);
17267             savedDetails[i] = NULL;
17268         }
17269         for(i=framePtr; i<MAX_MOVES; i++) {
17270                 if(commentList[i]) free(commentList[i]);
17271                 commentList[i] = NULL;
17272         }
17273         framePtr = MAX_MOVES-1;
17274         storedGames = 0;
17275 }
17276
17277 void
17278 LoadVariation (int index, char *text)
17279 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17280         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17281         int level = 0, move;
17282
17283         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17284         // first find outermost bracketing variation
17285         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17286             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17287                 if(*p == '{') wait = '}'; else
17288                 if(*p == '[') wait = ']'; else
17289                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17290                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17291             }
17292             if(*p == wait) wait = NULLCHAR; // closing ]} found
17293             p++;
17294         }
17295         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17296         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17297         end[1] = NULLCHAR; // clip off comment beyond variation
17298         ToNrEvent(currentMove-1);
17299         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17300         // kludge: use ParsePV() to append variation to game
17301         move = currentMove;
17302         ParsePV(start, TRUE, TRUE);
17303         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17304         ClearPremoveHighlights();
17305         CommentPopDown();
17306         ToNrEvent(currentMove+1);
17307 }
17308