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