Fix LoadGameOrPosition starting up engine
[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 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 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
172                                                                                 Board board));
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176                    /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188                            char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190                         int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
197
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397   case VariantGrand:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 Boolean shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 ChessSquare GrandArray[2][BOARD_FILES] = {
602     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
603         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
604     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
605         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
606 };
607
608 #ifdef GOTHIC
609 ChessSquare GothicArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
611         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
613         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !GOTHIC
616 #define GothicArray CapablancaArray
617 #endif // !GOTHIC
618
619 #ifdef FALCON
620 ChessSquare FalconArray[2][BOARD_FILES] = {
621     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
622         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
624         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
625 };
626 #else // !FALCON
627 #define FalconArray CapablancaArray
628 #endif // !FALCON
629
630 #else // !(BOARD_FILES>=10)
631 #define XiangqiPosition FIDEArray
632 #define CapablancaArray FIDEArray
633 #define GothicArray FIDEArray
634 #define GreatArray FIDEArray
635 #endif // !(BOARD_FILES>=10)
636
637 #if (BOARD_FILES>=12)
638 ChessSquare CourierArray[2][BOARD_FILES] = {
639     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
640         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
641     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
642         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
643 };
644 #else // !(BOARD_FILES>=12)
645 #define CourierArray CapablancaArray
646 #endif // !(BOARD_FILES>=12)
647
648
649 Board initialPosition;
650
651
652 /* Convert str to a rating. Checks for special cases of "----",
653
654    "++++", etc. Also strips ()'s */
655 int
656 string_to_rating(str)
657   char *str;
658 {
659   while(*str && !isdigit(*str)) ++str;
660   if (!*str)
661     return 0;   /* One of the special "no rating" cases */
662   else
663     return atoi(str);
664 }
665
666 void
667 ClearProgramStats()
668 {
669     /* Init programStats */
670     programStats.movelist[0] = 0;
671     programStats.depth = 0;
672     programStats.nr_moves = 0;
673     programStats.moves_left = 0;
674     programStats.nodes = 0;
675     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
676     programStats.score = 0;
677     programStats.got_only_move = 0;
678     programStats.got_fail = 0;
679     programStats.line_is_book = 0;
680 }
681
682 void
683 CommonEngineInit()
684 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
685     if (appData.firstPlaysBlack) {
686         first.twoMachinesColor = "black\n";
687         second.twoMachinesColor = "white\n";
688     } else {
689         first.twoMachinesColor = "white\n";
690         second.twoMachinesColor = "black\n";
691     }
692
693     first.other = &second;
694     second.other = &first;
695
696     { float norm = 1;
697         if(appData.timeOddsMode) {
698             norm = appData.timeOdds[0];
699             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
700         }
701         first.timeOdds  = appData.timeOdds[0]/norm;
702         second.timeOdds = appData.timeOdds[1]/norm;
703     }
704
705     if(programVersion) free(programVersion);
706     if (appData.noChessProgram) {
707         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
708         sprintf(programVersion, "%s", PACKAGE_STRING);
709     } else {
710       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
711       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
712       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
713     }
714 }
715
716 void
717 UnloadEngine(ChessProgramState *cps)
718 {
719         /* Kill off first chess program */
720         if (cps->isr != NULL)
721           RemoveInputSource(cps->isr);
722         cps->isr = NULL;
723
724         if (cps->pr != NoProc) {
725             ExitAnalyzeMode();
726             DoSleep( appData.delayBeforeQuit );
727             SendToProgram("quit\n", cps);
728             DoSleep( appData.delayAfterQuit );
729             DestroyChildProcess(cps->pr, cps->useSigterm);
730         }
731         cps->pr = NoProc;
732         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
733 }
734
735 void
736 ClearOptions(ChessProgramState *cps)
737 {
738     int i;
739     cps->nrOptions = cps->comboCnt = 0;
740     for(i=0; i<MAX_OPTIONS; i++) {
741         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
742         cps->option[i].textValue = 0;
743     }
744 }
745
746 char *engineNames[] = {
747 "first",
748 "second"
749 };
750
751 void
752 InitEngine(ChessProgramState *cps, int n)
753 {   // [HGM] all engine initialiation put in a function that does one engine
754
755     ClearOptions(cps);
756
757     cps->which = engineNames[n];
758     cps->maybeThinking = FALSE;
759     cps->pr = NoProc;
760     cps->isr = NULL;
761     cps->sendTime = 2;
762     cps->sendDrawOffers = 1;
763
764     cps->program = appData.chessProgram[n];
765     cps->host = appData.host[n];
766     cps->dir = appData.directory[n];
767     cps->initString = appData.engInitString[n];
768     cps->computerString = appData.computerString[n];
769     cps->useSigint  = TRUE;
770     cps->useSigterm = TRUE;
771     cps->reuse = appData.reuse[n];
772     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
773     cps->useSetboard = FALSE;
774     cps->useSAN = FALSE;
775     cps->usePing = FALSE;
776     cps->lastPing = 0;
777     cps->lastPong = 0;
778     cps->usePlayother = FALSE;
779     cps->useColors = TRUE;
780     cps->useUsermove = FALSE;
781     cps->sendICS = FALSE;
782     cps->sendName = appData.icsActive;
783     cps->sdKludge = FALSE;
784     cps->stKludge = FALSE;
785     TidyProgramName(cps->program, cps->host, cps->tidy);
786     cps->matchWins = 0;
787     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
788     cps->analysisSupport = 2; /* detect */
789     cps->analyzing = FALSE;
790     cps->initDone = FALSE;
791
792     /* New features added by Tord: */
793     cps->useFEN960 = FALSE;
794     cps->useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     cps->fenOverride  = appData.fenOverride[n];
797
798     /* [HGM] time odds: set factor for each machine */
799     cps->timeOdds  = appData.timeOdds[n];
800
801     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802     cps->accumulateTC = appData.accumulateTC[n];
803     cps->maxNrOfSessions = 1;
804
805     /* [HGM] debug */
806     cps->debug = FALSE;
807
808     cps->supportsNPS = UNKNOWN;
809     cps->memSize = FALSE;
810     cps->maxCores = FALSE;
811     cps->egtFormats[0] = NULLCHAR;
812
813     /* [HGM] options */
814     cps->optionSettings  = appData.engOptions[n];
815
816     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817     cps->isUCI = appData.isUCI[n]; /* [AS] */
818     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819
820     if (appData.protocolVersion[n] > PROTOVER
821         || appData.protocolVersion[n] < 1)
822       {
823         char buf[MSG_SIZ];
824         int len;
825
826         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827                        appData.protocolVersion[n]);
828         if( (len > MSG_SIZ) && appData.debugMode )
829           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830
831         DisplayFatalError(buf, 0, 2);
832       }
833     else
834       {
835         cps->protocolVersion = appData.protocolVersion[n];
836       }
837
838     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine(ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] = 
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
883
884 void
885 Load(ChessProgramState *cps, int i)
886 {
887     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
888     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
889         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
890         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
891         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
892         ParseArgsFromString(buf);
893         SwapEngines(i);
894         ReplaceEngine(cps, i);
895         return;
896     }
897     p = engineName;
898     while(q = strchr(p, SLASH)) p = q+1;
899     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
900     if(engineDir[0] != NULLCHAR)
901         appData.directory[i] = engineDir;
902     else if(p != engineName) { // derive directory from engine path, when not given
903         p[-1] = 0;
904         appData.directory[i] = strdup(engineName);
905         p[-1] = SLASH;
906     } else appData.directory[i] = ".";
907     if(params[0]) {
908         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
909         snprintf(command, MSG_SIZ, "%s %s", p, params);
910         p = command;
911     }
912     appData.chessProgram[i] = strdup(p);
913     appData.isUCI[i] = isUCI;
914     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
915     appData.hasOwnBookUCI[i] = hasBook;
916     if(!nickName[0]) useNick = FALSE;
917     if(useNick) ASSIGN(appData.pgnName[i], nickName);
918     if(addToList) {
919         int len;
920         char quote;
921         q = firstChessProgramNames;
922         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
923         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
924         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
925                         quote, p, quote, appData.directory[i], 
926                         useNick ? " -fn \"" : "",
927                         useNick ? nickName : "",
928                         useNick ? "\"" : "",
929                         v1 ? " -firstProtocolVersion 1" : "",
930                         hasBook ? "" : " -fNoOwnBookUCI",
931                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
932                         storeVariant ? " -variant " : "",
933                         storeVariant ? VariantName(gameInfo.variant) : "");
934         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
935         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
936         if(q)   free(q);
937     }
938     ReplaceEngine(cps, i);
939 }
940
941 void
942 InitTimeControls()
943 {
944     int matched, min, sec;
945     /*
946      * Parse timeControl resource
947      */
948     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
949                           appData.movesPerSession)) {
950         char buf[MSG_SIZ];
951         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
952         DisplayFatalError(buf, 0, 2);
953     }
954
955     /*
956      * Parse searchTime resource
957      */
958     if (*appData.searchTime != NULLCHAR) {
959         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
960         if (matched == 1) {
961             searchTime = min * 60;
962         } else if (matched == 2) {
963             searchTime = min * 60 + sec;
964         } else {
965             char buf[MSG_SIZ];
966             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
967             DisplayFatalError(buf, 0, 2);
968         }
969     }
970 }
971
972 void
973 InitBackEnd1()
974 {
975
976     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
977     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
978
979     GetTimeMark(&programStartTime);
980     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
981     appData.seedBase = random() + (random()<<15);
982     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
983
984     ClearProgramStats();
985     programStats.ok_to_send = 1;
986     programStats.seen_stat = 0;
987
988     /*
989      * Initialize game list
990      */
991     ListNew(&gameList);
992
993
994     /*
995      * Internet chess server status
996      */
997     if (appData.icsActive) {
998         appData.matchMode = FALSE;
999         appData.matchGames = 0;
1000 #if ZIPPY
1001         appData.noChessProgram = !appData.zippyPlay;
1002 #else
1003         appData.zippyPlay = FALSE;
1004         appData.zippyTalk = FALSE;
1005         appData.noChessProgram = TRUE;
1006 #endif
1007         if (*appData.icsHelper != NULLCHAR) {
1008             appData.useTelnet = TRUE;
1009             appData.telnetProgram = appData.icsHelper;
1010         }
1011     } else {
1012         appData.zippyTalk = appData.zippyPlay = FALSE;
1013     }
1014
1015     /* [AS] Initialize pv info list [HGM] and game state */
1016     {
1017         int i, j;
1018
1019         for( i=0; i<=framePtr; i++ ) {
1020             pvInfoList[i].depth = -1;
1021             boards[i][EP_STATUS] = EP_NONE;
1022             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1023         }
1024     }
1025
1026     InitTimeControls();
1027
1028     /* [AS] Adjudication threshold */
1029     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1030
1031     InitEngine(&first, 0);
1032     InitEngine(&second, 1);
1033     CommonEngineInit();
1034
1035     pairing.which = "pairing"; // pairing engine
1036     pairing.pr = NoProc;
1037     pairing.isr = NULL;
1038     pairing.program = appData.pairingEngine;
1039     pairing.host = "localhost";
1040     pairing.dir = ".";
1041
1042     if (appData.icsActive) {
1043         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1044     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1045         appData.clockMode = FALSE;
1046         first.sendTime = second.sendTime = 0;
1047     }
1048
1049 #if ZIPPY
1050     /* Override some settings from environment variables, for backward
1051        compatibility.  Unfortunately it's not feasible to have the env
1052        vars just set defaults, at least in xboard.  Ugh.
1053     */
1054     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1055       ZippyInit();
1056     }
1057 #endif
1058
1059     if (!appData.icsActive) {
1060       char buf[MSG_SIZ];
1061       int len;
1062
1063       /* Check for variants that are supported only in ICS mode,
1064          or not at all.  Some that are accepted here nevertheless
1065          have bugs; see comments below.
1066       */
1067       VariantClass variant = StringToVariant(appData.variant);
1068       switch (variant) {
1069       case VariantBughouse:     /* need four players and two boards */
1070       case VariantKriegspiel:   /* need to hide pieces and move details */
1071         /* case VariantFischeRandom: (Fabien: moved below) */
1072         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1073         if( (len > MSG_SIZ) && appData.debugMode )
1074           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1075
1076         DisplayFatalError(buf, 0, 2);
1077         return;
1078
1079       case VariantUnknown:
1080       case VariantLoadable:
1081       case Variant29:
1082       case Variant30:
1083       case Variant31:
1084       case Variant32:
1085       case Variant33:
1086       case Variant34:
1087       case Variant35:
1088       case Variant36:
1089       default:
1090         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1091         if( (len > MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1098       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1099       case VariantGothic:     /* [HGM] should work */
1100       case VariantCapablanca: /* [HGM] should work */
1101       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1102       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1103       case VariantKnightmate: /* [HGM] should work */
1104       case VariantCylinder:   /* [HGM] untested */
1105       case VariantFalcon:     /* [HGM] untested */
1106       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1107                                  offboard interposition not understood */
1108       case VariantNormal:     /* definitely works! */
1109       case VariantWildCastle: /* pieces not automatically shuffled */
1110       case VariantNoCastle:   /* pieces not automatically shuffled */
1111       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1112       case VariantLosers:     /* should work except for win condition,
1113                                  and doesn't know captures are mandatory */
1114       case VariantSuicide:    /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantGiveaway:   /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantTwoKings:   /* should work */
1119       case VariantAtomic:     /* should work except for win condition */
1120       case Variant3Check:     /* should work except for win condition */
1121       case VariantShatranj:   /* should work except for all win conditions */
1122       case VariantMakruk:     /* should work except for draw countdown */
1123       case VariantBerolina:   /* might work if TestLegality is off */
1124       case VariantCapaRandom: /* should work */
1125       case VariantJanus:      /* should work */
1126       case VariantSuper:      /* experimental */
1127       case VariantGreat:      /* experimental, requires legality testing to be off */
1128       case VariantSChess:     /* S-Chess, should work */
1129       case VariantGrand:      /* should work */
1130       case VariantSpartan:    /* should work */
1131         break;
1132       }
1133     }
1134
1135 }
1136
1137 int NextIntegerFromString( char ** str, long * value )
1138 {
1139     int result = -1;
1140     char * s = *str;
1141
1142     while( *s == ' ' || *s == '\t' ) {
1143         s++;
1144     }
1145
1146     *value = 0;
1147
1148     if( *s >= '0' && *s <= '9' ) {
1149         while( *s >= '0' && *s <= '9' ) {
1150             *value = *value * 10 + (*s - '0');
1151             s++;
1152         }
1153
1154         result = 0;
1155     }
1156
1157     *str = s;
1158
1159     return result;
1160 }
1161
1162 int NextTimeControlFromString( char ** str, long * value )
1163 {
1164     long temp;
1165     int result = NextIntegerFromString( str, &temp );
1166
1167     if( result == 0 ) {
1168         *value = temp * 60; /* Minutes */
1169         if( **str == ':' ) {
1170             (*str)++;
1171             result = NextIntegerFromString( str, &temp );
1172             *value += temp; /* Seconds */
1173         }
1174     }
1175
1176     return result;
1177 }
1178
1179 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1180 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1181     int result = -1, type = 0; long temp, temp2;
1182
1183     if(**str != ':') return -1; // old params remain in force!
1184     (*str)++;
1185     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1186     if( NextIntegerFromString( str, &temp ) ) return -1;
1187     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1188
1189     if(**str != '/') {
1190         /* time only: incremental or sudden-death time control */
1191         if(**str == '+') { /* increment follows; read it */
1192             (*str)++;
1193             if(**str == '!') type = *(*str)++; // Bronstein TC
1194             if(result = NextIntegerFromString( str, &temp2)) return -1;
1195             *inc = temp2 * 1000;
1196             if(**str == '.') { // read fraction of increment
1197                 char *start = ++(*str);
1198                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1199                 temp2 *= 1000;
1200                 while(start++ < *str) temp2 /= 10;
1201                 *inc += temp2;
1202             }
1203         } else *inc = 0;
1204         *moves = 0; *tc = temp * 1000; *incType = type;
1205         return 0;
1206     }
1207
1208     (*str)++; /* classical time control */
1209     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210
1211     if(result == 0) {
1212         *moves = temp;
1213         *tc    = temp2 * 1000;
1214         *inc   = 0;
1215         *incType = type;
1216     }
1217     return result;
1218 }
1219
1220 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1221 {   /* [HGM] get time to add from the multi-session time-control string */
1222     int incType, moves=1; /* kludge to force reading of first session */
1223     long time, increment;
1224     char *s = tcString;
1225
1226     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1227     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1228     do {
1229         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1230         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1231         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1232         if(movenr == -1) return time;    /* last move before new session     */
1233         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1234         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1235         if(!moves) return increment;     /* current session is incremental   */
1236         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1237     } while(movenr >= -1);               /* try again for next session       */
1238
1239     return 0; // no new time quota on this move
1240 }
1241
1242 int
1243 ParseTimeControl(tc, ti, mps)
1244      char *tc;
1245      float ti;
1246      int mps;
1247 {
1248   long tc1;
1249   long tc2;
1250   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1251   int min, sec=0;
1252
1253   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1254   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1255       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1256   if(ti > 0) {
1257
1258     if(mps)
1259       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1260     else 
1261       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1262   } else {
1263     if(mps)
1264       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1265     else 
1266       snprintf(buf, MSG_SIZ, ":%s", mytc);
1267   }
1268   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1269   
1270   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1271     return FALSE;
1272   }
1273
1274   if( *tc == '/' ) {
1275     /* Parse second time control */
1276     tc++;
1277
1278     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1279       return FALSE;
1280     }
1281
1282     if( tc2 == 0 ) {
1283       return FALSE;
1284     }
1285
1286     timeControl_2 = tc2 * 1000;
1287   }
1288   else {
1289     timeControl_2 = 0;
1290   }
1291
1292   if( tc1 == 0 ) {
1293     return FALSE;
1294   }
1295
1296   timeControl = tc1 * 1000;
1297
1298   if (ti >= 0) {
1299     timeIncrement = ti * 1000;  /* convert to ms */
1300     movesPerSession = 0;
1301   } else {
1302     timeIncrement = 0;
1303     movesPerSession = mps;
1304   }
1305   return TRUE;
1306 }
1307
1308 void
1309 InitBackEnd2()
1310 {
1311     if (appData.debugMode) {
1312         fprintf(debugFP, "%s\n", programVersion);
1313     }
1314
1315     set_cont_sequence(appData.wrapContSeq);
1316     if (appData.matchGames > 0) {
1317         appData.matchMode = TRUE;
1318     } else if (appData.matchMode) {
1319         appData.matchGames = 1;
1320     }
1321     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1322         appData.matchGames = appData.sameColorGames;
1323     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1324         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1325         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1326     }
1327     Reset(TRUE, FALSE);
1328     if (appData.noChessProgram || first.protocolVersion == 1) {
1329       InitBackEnd3();
1330     } else {
1331       /* kludge: allow timeout for initial "feature" commands */
1332       FreezeUI();
1333       DisplayMessage("", _("Starting chess program"));
1334       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1335     }
1336 }
1337
1338 int
1339 CalculateIndex(int index, int gameNr)
1340 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1341     int res;
1342     if(index > 0) return index; // fixed nmber
1343     if(index == 0) return 1;
1344     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1345     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1346     return res;
1347 }
1348
1349 int
1350 LoadGameOrPosition(int gameNr)
1351 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1352     if (*appData.loadGameFile != NULLCHAR) {
1353         if (!LoadGameFromFile(appData.loadGameFile,
1354                 CalculateIndex(appData.loadGameIndex, gameNr),
1355                               appData.loadGameFile, FALSE)) {
1356             DisplayFatalError(_("Bad game file"), 0, 1);
1357             return 0;
1358         }
1359     } else if (*appData.loadPositionFile != NULLCHAR) {
1360         if (!LoadPositionFromFile(appData.loadPositionFile,
1361                 CalculateIndex(appData.loadPositionIndex, gameNr),
1362                                   appData.loadPositionFile)) {
1363             DisplayFatalError(_("Bad position file"), 0, 1);
1364             return 0;
1365         }
1366     }
1367     return 1;
1368 }
1369
1370 void
1371 ReserveGame(int gameNr, char resChar)
1372 {
1373     FILE *tf = fopen(appData.tourneyFile, "r+");
1374     char *p, *q, c, buf[MSG_SIZ];
1375     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1376     safeStrCpy(buf, lastMsg, MSG_SIZ);
1377     DisplayMessage(_("Pick new game"), "");
1378     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1379     ParseArgsFromFile(tf);
1380     p = q = appData.results;
1381     if(appData.debugMode) {
1382       char *r = appData.participants;
1383       fprintf(debugFP, "results = '%s'\n", p);
1384       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1385       fprintf(debugFP, "\n");
1386     }
1387     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1388     nextGame = q - p;
1389     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1390     safeStrCpy(q, p, strlen(p) + 2);
1391     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1392     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1393     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1394         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1395         q[nextGame] = '*';
1396     }
1397     fseek(tf, -(strlen(p)+4), SEEK_END);
1398     c = fgetc(tf);
1399     if(c != '"') // depending on DOS or Unix line endings we can be one off
1400          fseek(tf, -(strlen(p)+2), SEEK_END);
1401     else fseek(tf, -(strlen(p)+3), SEEK_END);
1402     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1403     DisplayMessage(buf, "");
1404     free(p); appData.results = q;
1405     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1406        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1407         UnloadEngine(&first);  // next game belongs to other pairing;
1408         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1409     }
1410 }
1411
1412 void
1413 MatchEvent(int mode)
1414 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1415         int dummy;
1416         if(matchMode) { // already in match mode: switch it off
1417             abortMatch = TRUE;
1418             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1419             return;
1420         }
1421 //      if(gameMode != BeginningOfGame) {
1422 //          DisplayError(_("You can only start a match from the initial position."), 0);
1423 //          return;
1424 //      }
1425         abortMatch = FALSE;
1426         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1427         /* Set up machine vs. machine match */
1428         nextGame = 0;
1429         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1430         if(appData.tourneyFile[0]) {
1431             ReserveGame(-1, 0);
1432             if(nextGame > appData.matchGames) {
1433                 char buf[MSG_SIZ];
1434                 if(strchr(appData.results, '*') == NULL) {
1435                     FILE *f;
1436                     appData.tourneyCycles++;
1437                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1438                         fclose(f);
1439                         NextTourneyGame(-1, &dummy);
1440                         ReserveGame(-1, 0);
1441                         if(nextGame <= appData.matchGames) {
1442                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1443                             matchMode = mode;
1444                             ScheduleDelayedEvent(NextMatchGame, 10000);
1445                             return;
1446                         }
1447                     }
1448                 }
1449                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1450                 DisplayError(buf, 0);
1451                 appData.tourneyFile[0] = 0;
1452                 return;
1453             }
1454         } else
1455         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1456             DisplayFatalError(_("Can't have a match with no chess programs"),
1457                               0, 2);
1458             return;
1459         }
1460         matchMode = mode;
1461         matchGame = roundNr = 1;
1462         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1463         NextMatchGame();
1464 }
1465
1466 void
1467 InitBackEnd3 P((void))
1468 {
1469     GameMode initialMode;
1470     char buf[MSG_SIZ];
1471     int err, len;
1472
1473     InitChessProgram(&first, startedFromSetupPosition);
1474
1475     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1476         free(programVersion);
1477         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1478         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1479     }
1480
1481     if (appData.icsActive) {
1482 #ifdef WIN32
1483         /* [DM] Make a console window if needed [HGM] merged ifs */
1484         ConsoleCreate();
1485 #endif
1486         err = establish();
1487         if (err != 0)
1488           {
1489             if (*appData.icsCommPort != NULLCHAR)
1490               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1491                              appData.icsCommPort);
1492             else
1493               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1494                         appData.icsHost, appData.icsPort);
1495
1496             if( (len > MSG_SIZ) && appData.debugMode )
1497               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498
1499             DisplayFatalError(buf, err, 1);
1500             return;
1501         }
1502         SetICSMode();
1503         telnetISR =
1504           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1505         fromUserISR =
1506           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1507         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1508             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1509     } else if (appData.noChessProgram) {
1510         SetNCPMode();
1511     } else {
1512         SetGNUMode();
1513     }
1514
1515     if (*appData.cmailGameName != NULLCHAR) {
1516         SetCmailMode();
1517         OpenLoopback(&cmailPR);
1518         cmailISR =
1519           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1520     }
1521
1522     ThawUI();
1523     DisplayMessage("", "");
1524     if (StrCaseCmp(appData.initialMode, "") == 0) {
1525       initialMode = BeginningOfGame;
1526       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1527         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1528         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1529         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1530         ModeHighlight();
1531       }
1532     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1533       initialMode = TwoMachinesPlay;
1534     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1535       initialMode = AnalyzeFile;
1536     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1537       initialMode = AnalyzeMode;
1538     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1539       initialMode = MachinePlaysWhite;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1541       initialMode = MachinePlaysBlack;
1542     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1543       initialMode = EditGame;
1544     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1545       initialMode = EditPosition;
1546     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1547       initialMode = Training;
1548     } else {
1549       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1550       if( (len > MSG_SIZ) && appData.debugMode )
1551         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1552
1553       DisplayFatalError(buf, 0, 2);
1554       return;
1555     }
1556
1557     if (appData.matchMode) {
1558         if(appData.tourneyFile[0]) { // start tourney from command line
1559             FILE *f;
1560             if(f = fopen(appData.tourneyFile, "r")) {
1561                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1562                 fclose(f);
1563                 appData.clockMode = TRUE;
1564                 SetGNUMode();
1565             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1566         }
1567         MatchEvent(TRUE);
1568     } else if (*appData.cmailGameName != NULLCHAR) {
1569         /* Set up cmail mode */
1570         ReloadCmailMsgEvent(TRUE);
1571     } else {
1572         /* Set up other modes */
1573         if (initialMode == AnalyzeFile) {
1574           if (*appData.loadGameFile == NULLCHAR) {
1575             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1576             return;
1577           }
1578         }
1579         if (*appData.loadGameFile != NULLCHAR) {
1580             (void) LoadGameFromFile(appData.loadGameFile,
1581                                     appData.loadGameIndex,
1582                                     appData.loadGameFile, TRUE);
1583         } else if (*appData.loadPositionFile != NULLCHAR) {
1584             (void) LoadPositionFromFile(appData.loadPositionFile,
1585                                         appData.loadPositionIndex,
1586                                         appData.loadPositionFile);
1587             /* [HGM] try to make self-starting even after FEN load */
1588             /* to allow automatic setup of fairy variants with wtm */
1589             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1590                 gameMode = BeginningOfGame;
1591                 setboardSpoiledMachineBlack = 1;
1592             }
1593             /* [HGM] loadPos: make that every new game uses the setup */
1594             /* from file as long as we do not switch variant          */
1595             if(!blackPlaysFirst) {
1596                 startedFromPositionFile = TRUE;
1597                 CopyBoard(filePosition, boards[0]);
1598             }
1599         }
1600         if (initialMode == AnalyzeMode) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1603             return;
1604           }
1605           if (appData.icsActive) {
1606             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1607             return;
1608           }
1609           AnalyzeModeEvent();
1610         } else if (initialMode == AnalyzeFile) {
1611           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1612           ShowThinkingEvent();
1613           AnalyzeFileEvent();
1614           AnalysisPeriodicEvent(1);
1615         } else if (initialMode == MachinePlaysWhite) {
1616           if (appData.noChessProgram) {
1617             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1618                               0, 2);
1619             return;
1620           }
1621           if (appData.icsActive) {
1622             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1623                               0, 2);
1624             return;
1625           }
1626           MachineWhiteEvent();
1627         } else if (initialMode == MachinePlaysBlack) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1630                               0, 2);
1631             return;
1632           }
1633           if (appData.icsActive) {
1634             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1635                               0, 2);
1636             return;
1637           }
1638           MachineBlackEvent();
1639         } else if (initialMode == TwoMachinesPlay) {
1640           if (appData.noChessProgram) {
1641             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1642                               0, 2);
1643             return;
1644           }
1645           if (appData.icsActive) {
1646             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1647                               0, 2);
1648             return;
1649           }
1650           TwoMachinesEvent();
1651         } else if (initialMode == EditGame) {
1652           EditGameEvent();
1653         } else if (initialMode == EditPosition) {
1654           EditPositionEvent();
1655         } else if (initialMode == Training) {
1656           if (*appData.loadGameFile == NULLCHAR) {
1657             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658             return;
1659           }
1660           TrainingEvent();
1661         }
1662     }
1663 }
1664
1665 void
1666 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1667 {
1668     MoveHistorySet( movelist, first, last, current, pvInfoList );
1669
1670     EvalGraphSet( first, last, current, pvInfoList );
1671
1672     MakeEngineOutputTitle();
1673 }
1674
1675 /*
1676  * Establish will establish a contact to a remote host.port.
1677  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1678  *  used to talk to the host.
1679  * Returns 0 if okay, error code if not.
1680  */
1681 int
1682 establish()
1683 {
1684     char buf[MSG_SIZ];
1685
1686     if (*appData.icsCommPort != NULLCHAR) {
1687         /* Talk to the host through a serial comm port */
1688         return OpenCommPort(appData.icsCommPort, &icsPR);
1689
1690     } else if (*appData.gateway != NULLCHAR) {
1691         if (*appData.remoteShell == NULLCHAR) {
1692             /* Use the rcmd protocol to run telnet program on a gateway host */
1693             snprintf(buf, sizeof(buf), "%s %s %s",
1694                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1695             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1696
1697         } else {
1698             /* Use the rsh program to run telnet program on a gateway host */
1699             if (*appData.remoteUser == NULLCHAR) {
1700                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1701                         appData.gateway, appData.telnetProgram,
1702                         appData.icsHost, appData.icsPort);
1703             } else {
1704                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1705                         appData.remoteShell, appData.gateway,
1706                         appData.remoteUser, appData.telnetProgram,
1707                         appData.icsHost, appData.icsPort);
1708             }
1709             return StartChildProcess(buf, "", &icsPR);
1710
1711         }
1712     } else if (appData.useTelnet) {
1713         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1714
1715     } else {
1716         /* TCP socket interface differs somewhat between
1717            Unix and NT; handle details in the front end.
1718            */
1719         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1720     }
1721 }
1722
1723 void EscapeExpand(char *p, char *q)
1724 {       // [HGM] initstring: routine to shape up string arguments
1725         while(*p++ = *q++) if(p[-1] == '\\')
1726             switch(*q++) {
1727                 case 'n': p[-1] = '\n'; break;
1728                 case 'r': p[-1] = '\r'; break;
1729                 case 't': p[-1] = '\t'; break;
1730                 case '\\': p[-1] = '\\'; break;
1731                 case 0: *p = 0; return;
1732                 default: p[-1] = q[-1]; break;
1733             }
1734 }
1735
1736 void
1737 show_bytes(fp, buf, count)
1738      FILE *fp;
1739      char *buf;
1740      int count;
1741 {
1742     while (count--) {
1743         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1744             fprintf(fp, "\\%03o", *buf & 0xff);
1745         } else {
1746             putc(*buf, fp);
1747         }
1748         buf++;
1749     }
1750     fflush(fp);
1751 }
1752
1753 /* Returns an errno value */
1754 int
1755 OutputMaybeTelnet(pr, message, count, outError)
1756      ProcRef pr;
1757      char *message;
1758      int count;
1759      int *outError;
1760 {
1761     char buf[8192], *p, *q, *buflim;
1762     int left, newcount, outcount;
1763
1764     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1765         *appData.gateway != NULLCHAR) {
1766         if (appData.debugMode) {
1767             fprintf(debugFP, ">ICS: ");
1768             show_bytes(debugFP, message, count);
1769             fprintf(debugFP, "\n");
1770         }
1771         return OutputToProcess(pr, message, count, outError);
1772     }
1773
1774     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1775     p = message;
1776     q = buf;
1777     left = count;
1778     newcount = 0;
1779     while (left) {
1780         if (q >= buflim) {
1781             if (appData.debugMode) {
1782                 fprintf(debugFP, ">ICS: ");
1783                 show_bytes(debugFP, buf, newcount);
1784                 fprintf(debugFP, "\n");
1785             }
1786             outcount = OutputToProcess(pr, buf, newcount, outError);
1787             if (outcount < newcount) return -1; /* to be sure */
1788             q = buf;
1789             newcount = 0;
1790         }
1791         if (*p == '\n') {
1792             *q++ = '\r';
1793             newcount++;
1794         } else if (((unsigned char) *p) == TN_IAC) {
1795             *q++ = (char) TN_IAC;
1796             newcount ++;
1797         }
1798         *q++ = *p++;
1799         newcount++;
1800         left--;
1801     }
1802     if (appData.debugMode) {
1803         fprintf(debugFP, ">ICS: ");
1804         show_bytes(debugFP, buf, newcount);
1805         fprintf(debugFP, "\n");
1806     }
1807     outcount = OutputToProcess(pr, buf, newcount, outError);
1808     if (outcount < newcount) return -1; /* to be sure */
1809     return count;
1810 }
1811
1812 void
1813 read_from_player(isr, closure, message, count, error)
1814      InputSourceRef isr;
1815      VOIDSTAR closure;
1816      char *message;
1817      int count;
1818      int error;
1819 {
1820     int outError, outCount;
1821     static int gotEof = 0;
1822
1823     /* Pass data read from player on to ICS */
1824     if (count > 0) {
1825         gotEof = 0;
1826         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1827         if (outCount < count) {
1828             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1829         }
1830     } else if (count < 0) {
1831         RemoveInputSource(isr);
1832         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1833     } else if (gotEof++ > 0) {
1834         RemoveInputSource(isr);
1835         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1836     }
1837 }
1838
1839 void
1840 KeepAlive()
1841 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1842     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1843     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1844     SendToICS("date\n");
1845     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1846 }
1847
1848 /* added routine for printf style output to ics */
1849 void ics_printf(char *format, ...)
1850 {
1851     char buffer[MSG_SIZ];
1852     va_list args;
1853
1854     va_start(args, format);
1855     vsnprintf(buffer, sizeof(buffer), format, args);
1856     buffer[sizeof(buffer)-1] = '\0';
1857     SendToICS(buffer);
1858     va_end(args);
1859 }
1860
1861 void
1862 SendToICS(s)
1863      char *s;
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NULL) return;
1868
1869     count = strlen(s);
1870     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1871     if (outCount < count) {
1872         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1873     }
1874 }
1875
1876 /* This is used for sending logon scripts to the ICS. Sending
1877    without a delay causes problems when using timestamp on ICC
1878    (at least on my machine). */
1879 void
1880 SendToICSDelayed(s,msdelay)
1881      char *s;
1882      long msdelay;
1883 {
1884     int count, outCount, outError;
1885
1886     if (icsPR == NULL) return;
1887
1888     count = strlen(s);
1889     if (appData.debugMode) {
1890         fprintf(debugFP, ">ICS: ");
1891         show_bytes(debugFP, s, count);
1892         fprintf(debugFP, "\n");
1893     }
1894     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1895                                       msdelay);
1896     if (outCount < count) {
1897         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898     }
1899 }
1900
1901
1902 /* Remove all highlighting escape sequences in s
1903    Also deletes any suffix starting with '('
1904    */
1905 char *
1906 StripHighlightAndTitle(s)
1907      char *s;
1908 {
1909     static char retbuf[MSG_SIZ];
1910     char *p = retbuf;
1911
1912     while (*s != NULLCHAR) {
1913         while (*s == '\033') {
1914             while (*s != NULLCHAR && !isalpha(*s)) s++;
1915             if (*s != NULLCHAR) s++;
1916         }
1917         while (*s != NULLCHAR && *s != '\033') {
1918             if (*s == '(' || *s == '[') {
1919                 *p = NULLCHAR;
1920                 return retbuf;
1921             }
1922             *p++ = *s++;
1923         }
1924     }
1925     *p = NULLCHAR;
1926     return retbuf;
1927 }
1928
1929 /* Remove all highlighting escape sequences in s */
1930 char *
1931 StripHighlight(s)
1932      char *s;
1933 {
1934     static char retbuf[MSG_SIZ];
1935     char *p = retbuf;
1936
1937     while (*s != NULLCHAR) {
1938         while (*s == '\033') {
1939             while (*s != NULLCHAR && !isalpha(*s)) s++;
1940             if (*s != NULLCHAR) s++;
1941         }
1942         while (*s != NULLCHAR && *s != '\033') {
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 char *variantNames[] = VARIANT_NAMES;
1951 char *
1952 VariantName(v)
1953      VariantClass v;
1954 {
1955     return variantNames[v];
1956 }
1957
1958
1959 /* Identify a variant from the strings the chess servers use or the
1960    PGN Variant tag names we use. */
1961 VariantClass
1962 StringToVariant(e)
1963      char *e;
1964 {
1965     char *p;
1966     int wnum = -1;
1967     VariantClass v = VariantNormal;
1968     int i, found = FALSE;
1969     char buf[MSG_SIZ];
1970     int len;
1971
1972     if (!e) return v;
1973
1974     /* [HGM] skip over optional board-size prefixes */
1975     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1976         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1977         while( *e++ != '_');
1978     }
1979
1980     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1981         v = VariantNormal;
1982         found = TRUE;
1983     } else
1984     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1985       if (StrCaseStr(e, variantNames[i])) {
1986         v = (VariantClass) i;
1987         found = TRUE;
1988         break;
1989       }
1990     }
1991
1992     if (!found) {
1993       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1994           || StrCaseStr(e, "wild/fr")
1995           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1996         v = VariantFischeRandom;
1997       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1998                  (i = 1, p = StrCaseStr(e, "w"))) {
1999         p += i;
2000         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2001         if (isdigit(*p)) {
2002           wnum = atoi(p);
2003         } else {
2004           wnum = -1;
2005         }
2006         switch (wnum) {
2007         case 0: /* FICS only, actually */
2008         case 1:
2009           /* Castling legal even if K starts on d-file */
2010           v = VariantWildCastle;
2011           break;
2012         case 2:
2013         case 3:
2014         case 4:
2015           /* Castling illegal even if K & R happen to start in
2016              normal positions. */
2017           v = VariantNoCastle;
2018           break;
2019         case 5:
2020         case 7:
2021         case 8:
2022         case 10:
2023         case 11:
2024         case 12:
2025         case 13:
2026         case 14:
2027         case 15:
2028         case 18:
2029         case 19:
2030           /* Castling legal iff K & R start in normal positions */
2031           v = VariantNormal;
2032           break;
2033         case 6:
2034         case 20:
2035         case 21:
2036           /* Special wilds for position setup; unclear what to do here */
2037           v = VariantLoadable;
2038           break;
2039         case 9:
2040           /* Bizarre ICC game */
2041           v = VariantTwoKings;
2042           break;
2043         case 16:
2044           v = VariantKriegspiel;
2045           break;
2046         case 17:
2047           v = VariantLosers;
2048           break;
2049         case 22:
2050           v = VariantFischeRandom;
2051           break;
2052         case 23:
2053           v = VariantCrazyhouse;
2054           break;
2055         case 24:
2056           v = VariantBughouse;
2057           break;
2058         case 25:
2059           v = Variant3Check;
2060           break;
2061         case 26:
2062           /* Not quite the same as FICS suicide! */
2063           v = VariantGiveaway;
2064           break;
2065         case 27:
2066           v = VariantAtomic;
2067           break;
2068         case 28:
2069           v = VariantShatranj;
2070           break;
2071
2072         /* Temporary names for future ICC types.  The name *will* change in
2073            the next xboard/WinBoard release after ICC defines it. */
2074         case 29:
2075           v = Variant29;
2076           break;
2077         case 30:
2078           v = Variant30;
2079           break;
2080         case 31:
2081           v = Variant31;
2082           break;
2083         case 32:
2084           v = Variant32;
2085           break;
2086         case 33:
2087           v = Variant33;
2088           break;
2089         case 34:
2090           v = Variant34;
2091           break;
2092         case 35:
2093           v = Variant35;
2094           break;
2095         case 36:
2096           v = Variant36;
2097           break;
2098         case 37:
2099           v = VariantShogi;
2100           break;
2101         case 38:
2102           v = VariantXiangqi;
2103           break;
2104         case 39:
2105           v = VariantCourier;
2106           break;
2107         case 40:
2108           v = VariantGothic;
2109           break;
2110         case 41:
2111           v = VariantCapablanca;
2112           break;
2113         case 42:
2114           v = VariantKnightmate;
2115           break;
2116         case 43:
2117           v = VariantFairy;
2118           break;
2119         case 44:
2120           v = VariantCylinder;
2121           break;
2122         case 45:
2123           v = VariantFalcon;
2124           break;
2125         case 46:
2126           v = VariantCapaRandom;
2127           break;
2128         case 47:
2129           v = VariantBerolina;
2130           break;
2131         case 48:
2132           v = VariantJanus;
2133           break;
2134         case 49:
2135           v = VariantSuper;
2136           break;
2137         case 50:
2138           v = VariantGreat;
2139           break;
2140         case -1:
2141           /* Found "wild" or "w" in the string but no number;
2142              must assume it's normal chess. */
2143           v = VariantNormal;
2144           break;
2145         default:
2146           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2147           if( (len > MSG_SIZ) && appData.debugMode )
2148             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2149
2150           DisplayError(buf, 0);
2151           v = VariantUnknown;
2152           break;
2153         }
2154       }
2155     }
2156     if (appData.debugMode) {
2157       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2158               e, wnum, VariantName(v));
2159     }
2160     return v;
2161 }
2162
2163 static int leftover_start = 0, leftover_len = 0;
2164 char star_match[STAR_MATCH_N][MSG_SIZ];
2165
2166 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2167    advance *index beyond it, and set leftover_start to the new value of
2168    *index; else return FALSE.  If pattern contains the character '*', it
2169    matches any sequence of characters not containing '\r', '\n', or the
2170    character following the '*' (if any), and the matched sequence(s) are
2171    copied into star_match.
2172    */
2173 int
2174 looking_at(buf, index, pattern)
2175      char *buf;
2176      int *index;
2177      char *pattern;
2178 {
2179     char *bufp = &buf[*index], *patternp = pattern;
2180     int star_count = 0;
2181     char *matchp = star_match[0];
2182
2183     for (;;) {
2184         if (*patternp == NULLCHAR) {
2185             *index = leftover_start = bufp - buf;
2186             *matchp = NULLCHAR;
2187             return TRUE;
2188         }
2189         if (*bufp == NULLCHAR) return FALSE;
2190         if (*patternp == '*') {
2191             if (*bufp == *(patternp + 1)) {
2192                 *matchp = NULLCHAR;
2193                 matchp = star_match[++star_count];
2194                 patternp += 2;
2195                 bufp++;
2196                 continue;
2197             } else if (*bufp == '\n' || *bufp == '\r') {
2198                 patternp++;
2199                 if (*patternp == NULLCHAR)
2200                   continue;
2201                 else
2202                   return FALSE;
2203             } else {
2204                 *matchp++ = *bufp++;
2205                 continue;
2206             }
2207         }
2208         if (*patternp != *bufp) return FALSE;
2209         patternp++;
2210         bufp++;
2211     }
2212 }
2213
2214 void
2215 SendToPlayer(data, length)
2216      char *data;
2217      int length;
2218 {
2219     int error, outCount;
2220     outCount = OutputToProcess(NoProc, data, length, &error);
2221     if (outCount < length) {
2222         DisplayFatalError(_("Error writing to display"), error, 1);
2223     }
2224 }
2225
2226 void
2227 PackHolding(packed, holding)
2228      char packed[];
2229      char *holding;
2230 {
2231     char *p = holding;
2232     char *q = packed;
2233     int runlength = 0;
2234     int curr = 9999;
2235     do {
2236         if (*p == curr) {
2237             runlength++;
2238         } else {
2239             switch (runlength) {
2240               case 0:
2241                 break;
2242               case 1:
2243                 *q++ = curr;
2244                 break;
2245               case 2:
2246                 *q++ = curr;
2247                 *q++ = curr;
2248                 break;
2249               default:
2250                 sprintf(q, "%d", runlength);
2251                 while (*q) q++;
2252                 *q++ = curr;
2253                 break;
2254             }
2255             runlength = 1;
2256             curr = *p;
2257         }
2258     } while (*p++);
2259     *q = NULLCHAR;
2260 }
2261
2262 /* Telnet protocol requests from the front end */
2263 void
2264 TelnetRequest(ddww, option)
2265      unsigned char ddww, option;
2266 {
2267     unsigned char msg[3];
2268     int outCount, outError;
2269
2270     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2271
2272     if (appData.debugMode) {
2273         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2274         switch (ddww) {
2275           case TN_DO:
2276             ddwwStr = "DO";
2277             break;
2278           case TN_DONT:
2279             ddwwStr = "DONT";
2280             break;
2281           case TN_WILL:
2282             ddwwStr = "WILL";
2283             break;
2284           case TN_WONT:
2285             ddwwStr = "WONT";
2286             break;
2287           default:
2288             ddwwStr = buf1;
2289             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2290             break;
2291         }
2292         switch (option) {
2293           case TN_ECHO:
2294             optionStr = "ECHO";
2295             break;
2296           default:
2297             optionStr = buf2;
2298             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2299             break;
2300         }
2301         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2302     }
2303     msg[0] = TN_IAC;
2304     msg[1] = ddww;
2305     msg[2] = option;
2306     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2307     if (outCount < 3) {
2308         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2309     }
2310 }
2311
2312 void
2313 DoEcho()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DO, TN_ECHO);
2317 }
2318
2319 void
2320 DontEcho()
2321 {
2322     if (!appData.icsActive) return;
2323     TelnetRequest(TN_DONT, TN_ECHO);
2324 }
2325
2326 void
2327 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2328 {
2329     /* put the holdings sent to us by the server on the board holdings area */
2330     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2331     char p;
2332     ChessSquare piece;
2333
2334     if(gameInfo.holdingsWidth < 2)  return;
2335     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2336         return; // prevent overwriting by pre-board holdings
2337
2338     if( (int)lowestPiece >= BlackPawn ) {
2339         holdingsColumn = 0;
2340         countsColumn = 1;
2341         holdingsStartRow = BOARD_HEIGHT-1;
2342         direction = -1;
2343     } else {
2344         holdingsColumn = BOARD_WIDTH-1;
2345         countsColumn = BOARD_WIDTH-2;
2346         holdingsStartRow = 0;
2347         direction = 1;
2348     }
2349
2350     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2351         board[i][holdingsColumn] = EmptySquare;
2352         board[i][countsColumn]   = (ChessSquare) 0;
2353     }
2354     while( (p=*holdings++) != NULLCHAR ) {
2355         piece = CharToPiece( ToUpper(p) );
2356         if(piece == EmptySquare) continue;
2357         /*j = (int) piece - (int) WhitePawn;*/
2358         j = PieceToNumber(piece);
2359         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2360         if(j < 0) continue;               /* should not happen */
2361         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2362         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2363         board[holdingsStartRow+j*direction][countsColumn]++;
2364     }
2365 }
2366
2367
2368 void
2369 VariantSwitch(Board board, VariantClass newVariant)
2370 {
2371    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2372    static Board oldBoard;
2373
2374    startedFromPositionFile = FALSE;
2375    if(gameInfo.variant == newVariant) return;
2376
2377    /* [HGM] This routine is called each time an assignment is made to
2378     * gameInfo.variant during a game, to make sure the board sizes
2379     * are set to match the new variant. If that means adding or deleting
2380     * holdings, we shift the playing board accordingly
2381     * This kludge is needed because in ICS observe mode, we get boards
2382     * of an ongoing game without knowing the variant, and learn about the
2383     * latter only later. This can be because of the move list we requested,
2384     * in which case the game history is refilled from the beginning anyway,
2385     * but also when receiving holdings of a crazyhouse game. In the latter
2386     * case we want to add those holdings to the already received position.
2387     */
2388
2389
2390    if (appData.debugMode) {
2391      fprintf(debugFP, "Switch board from %s to %s\n",
2392              VariantName(gameInfo.variant), VariantName(newVariant));
2393      setbuf(debugFP, NULL);
2394    }
2395    shuffleOpenings = 0;       /* [HGM] shuffle */
2396    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2397    switch(newVariant)
2398      {
2399      case VariantShogi:
2400        newWidth = 9;  newHeight = 9;
2401        gameInfo.holdingsSize = 7;
2402      case VariantBughouse:
2403      case VariantCrazyhouse:
2404        newHoldingsWidth = 2; break;
2405      case VariantGreat:
2406        newWidth = 10;
2407      case VariantSuper:
2408        newHoldingsWidth = 2;
2409        gameInfo.holdingsSize = 8;
2410        break;
2411      case VariantGothic:
2412      case VariantCapablanca:
2413      case VariantCapaRandom:
2414        newWidth = 10;
2415      default:
2416        newHoldingsWidth = gameInfo.holdingsSize = 0;
2417      };
2418
2419    if(newWidth  != gameInfo.boardWidth  ||
2420       newHeight != gameInfo.boardHeight ||
2421       newHoldingsWidth != gameInfo.holdingsWidth ) {
2422
2423      /* shift position to new playing area, if needed */
2424      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429        for(i=0; i<newHeight; i++) {
2430          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2431          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2432        }
2433      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438      }
2439      gameInfo.boardWidth  = newWidth;
2440      gameInfo.boardHeight = newHeight;
2441      gameInfo.holdingsWidth = newHoldingsWidth;
2442      gameInfo.variant = newVariant;
2443      InitDrawingSizes(-2, 0);
2444    } else gameInfo.variant = newVariant;
2445    CopyBoard(oldBoard, board);   // remember correctly formatted board
2446      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2447    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2448 }
2449
2450 static int loggedOn = FALSE;
2451
2452 /*-- Game start info cache: --*/
2453 int gs_gamenum;
2454 char gs_kind[MSG_SIZ];
2455 static char player1Name[128] = "";
2456 static char player2Name[128] = "";
2457 static char cont_seq[] = "\n\\   ";
2458 static int player1Rating = -1;
2459 static int player2Rating = -1;
2460 /*----------------------------*/
2461
2462 ColorClass curColor = ColorNormal;
2463 int suppressKibitz = 0;
2464
2465 // [HGM] seekgraph
2466 Boolean soughtPending = FALSE;
2467 Boolean seekGraphUp;
2468 #define MAX_SEEK_ADS 200
2469 #define SQUARE 0x80
2470 char *seekAdList[MAX_SEEK_ADS];
2471 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2472 float tcList[MAX_SEEK_ADS];
2473 char colorList[MAX_SEEK_ADS];
2474 int nrOfSeekAds = 0;
2475 int minRating = 1010, maxRating = 2800;
2476 int hMargin = 10, vMargin = 20, h, w;
2477 extern int squareSize, lineGap;
2478
2479 void
2480 PlotSeekAd(int i)
2481 {
2482         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2483         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2484         if(r < minRating+100 && r >=0 ) r = minRating+100;
2485         if(r > maxRating) r = maxRating;
2486         if(tc < 1.) tc = 1.;
2487         if(tc > 95.) tc = 95.;
2488         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2489         y = ((double)r - minRating)/(maxRating - minRating)
2490             * (h-vMargin-squareSize/8-1) + vMargin;
2491         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2492         if(strstr(seekAdList[i], " u ")) color = 1;
2493         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2494            !strstr(seekAdList[i], "bullet") &&
2495            !strstr(seekAdList[i], "blitz") &&
2496            !strstr(seekAdList[i], "standard") ) color = 2;
2497         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2498         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2499 }
2500
2501 void
2502 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2503 {
2504         char buf[MSG_SIZ], *ext = "";
2505         VariantClass v = StringToVariant(type);
2506         if(strstr(type, "wild")) {
2507             ext = type + 4; // append wild number
2508             if(v == VariantFischeRandom) type = "chess960"; else
2509             if(v == VariantLoadable) type = "setup"; else
2510             type = VariantName(v);
2511         }
2512         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2513         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2514             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2515             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2516             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2517             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2518             seekNrList[nrOfSeekAds] = nr;
2519             zList[nrOfSeekAds] = 0;
2520             seekAdList[nrOfSeekAds++] = StrSave(buf);
2521             if(plot) PlotSeekAd(nrOfSeekAds-1);
2522         }
2523 }
2524
2525 void
2526 EraseSeekDot(int i)
2527 {
2528     int x = xList[i], y = yList[i], d=squareSize/4, k;
2529     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2530     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2531     // now replot every dot that overlapped
2532     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2533         int xx = xList[k], yy = yList[k];
2534         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2535             DrawSeekDot(xx, yy, colorList[k]);
2536     }
2537 }
2538
2539 void
2540 RemoveSeekAd(int nr)
2541 {
2542         int i;
2543         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2544             EraseSeekDot(i);
2545             if(seekAdList[i]) free(seekAdList[i]);
2546             seekAdList[i] = seekAdList[--nrOfSeekAds];
2547             seekNrList[i] = seekNrList[nrOfSeekAds];
2548             ratingList[i] = ratingList[nrOfSeekAds];
2549             colorList[i]  = colorList[nrOfSeekAds];
2550             tcList[i] = tcList[nrOfSeekAds];
2551             xList[i]  = xList[nrOfSeekAds];
2552             yList[i]  = yList[nrOfSeekAds];
2553             zList[i]  = zList[nrOfSeekAds];
2554             seekAdList[nrOfSeekAds] = NULL;
2555             break;
2556         }
2557 }
2558
2559 Boolean
2560 MatchSoughtLine(char *line)
2561 {
2562     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2563     int nr, base, inc, u=0; char dummy;
2564
2565     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2566        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2567        (u=1) &&
2568        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2570         // match: compact and save the line
2571         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2572         return TRUE;
2573     }
2574     return FALSE;
2575 }
2576
2577 int
2578 DrawSeekGraph()
2579 {
2580     int i;
2581     if(!seekGraphUp) return FALSE;
2582     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2583     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2584
2585     DrawSeekBackground(0, 0, w, h);
2586     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2587     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2588     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2589         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2590         yy = h-1-yy;
2591         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2592         if(i%500 == 0) {
2593             char buf[MSG_SIZ];
2594             snprintf(buf, MSG_SIZ, "%d", i);
2595             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2596         }
2597     }
2598     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2599     for(i=1; i<100; i+=(i<10?1:5)) {
2600         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2601         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2602         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2606         }
2607     }
2608     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2609     return TRUE;
2610 }
2611
2612 int SeekGraphClick(ClickType click, int x, int y, int moving)
2613 {
2614     static int lastDown = 0, displayed = 0, lastSecond;
2615     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2616         if(click == Release || moving) return FALSE;
2617         nrOfSeekAds = 0;
2618         soughtPending = TRUE;
2619         SendToICS(ics_prefix);
2620         SendToICS("sought\n"); // should this be "sought all"?
2621     } else { // issue challenge based on clicked ad
2622         int dist = 10000; int i, closest = 0, second = 0;
2623         for(i=0; i<nrOfSeekAds; i++) {
2624             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2625             if(d < dist) { dist = d; closest = i; }
2626             second += (d - zList[i] < 120); // count in-range ads
2627             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2628         }
2629         if(dist < 120) {
2630             char buf[MSG_SIZ];
2631             second = (second > 1);
2632             if(displayed != closest || second != lastSecond) {
2633                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2634                 lastSecond = second; displayed = closest;
2635             }
2636             if(click == Press) {
2637                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2638                 lastDown = closest;
2639                 return TRUE;
2640             } // on press 'hit', only show info
2641             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2642             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2643             SendToICS(ics_prefix);
2644             SendToICS(buf);
2645             return TRUE; // let incoming board of started game pop down the graph
2646         } else if(click == Release) { // release 'miss' is ignored
2647             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2648             if(moving == 2) { // right up-click
2649                 nrOfSeekAds = 0; // refresh graph
2650                 soughtPending = TRUE;
2651                 SendToICS(ics_prefix);
2652                 SendToICS("sought\n"); // should this be "sought all"?
2653             }
2654             return TRUE;
2655         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2656         // press miss or release hit 'pop down' seek graph
2657         seekGraphUp = FALSE;
2658         DrawPosition(TRUE, NULL);
2659     }
2660     return TRUE;
2661 }
2662
2663 void
2664 read_from_ics(isr, closure, data, count, error)
2665      InputSourceRef isr;
2666      VOIDSTAR closure;
2667      char *data;
2668      int count;
2669      int error;
2670 {
2671 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2672 #define STARTED_NONE 0
2673 #define STARTED_MOVES 1
2674 #define STARTED_BOARD 2
2675 #define STARTED_OBSERVE 3
2676 #define STARTED_HOLDINGS 4
2677 #define STARTED_CHATTER 5
2678 #define STARTED_COMMENT 6
2679 #define STARTED_MOVES_NOHIDE 7
2680
2681     static int started = STARTED_NONE;
2682     static char parse[20000];
2683     static int parse_pos = 0;
2684     static char buf[BUF_SIZE + 1];
2685     static int firstTime = TRUE, intfSet = FALSE;
2686     static ColorClass prevColor = ColorNormal;
2687     static int savingComment = FALSE;
2688     static int cmatch = 0; // continuation sequence match
2689     char *bp;
2690     char str[MSG_SIZ];
2691     int i, oldi;
2692     int buf_len;
2693     int next_out;
2694     int tkind;
2695     int backup;    /* [DM] For zippy color lines */
2696     char *p;
2697     char talker[MSG_SIZ]; // [HGM] chat
2698     int channel;
2699
2700     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2701
2702     if (appData.debugMode) {
2703       if (!error) {
2704         fprintf(debugFP, "<ICS: ");
2705         show_bytes(debugFP, data, count);
2706         fprintf(debugFP, "\n");
2707       }
2708     }
2709
2710     if (appData.debugMode) { int f = forwardMostMove;
2711         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2712                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2713                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2714     }
2715     if (count > 0) {
2716         /* If last read ended with a partial line that we couldn't parse,
2717            prepend it to the new read and try again. */
2718         if (leftover_len > 0) {
2719             for (i=0; i<leftover_len; i++)
2720               buf[i] = buf[leftover_start + i];
2721         }
2722
2723     /* copy new characters into the buffer */
2724     bp = buf + leftover_len;
2725     buf_len=leftover_len;
2726     for (i=0; i<count; i++)
2727     {
2728         // ignore these
2729         if (data[i] == '\r')
2730             continue;
2731
2732         // join lines split by ICS?
2733         if (!appData.noJoin)
2734         {
2735             /*
2736                 Joining just consists of finding matches against the
2737                 continuation sequence, and discarding that sequence
2738                 if found instead of copying it.  So, until a match
2739                 fails, there's nothing to do since it might be the
2740                 complete sequence, and thus, something we don't want
2741                 copied.
2742             */
2743             if (data[i] == cont_seq[cmatch])
2744             {
2745                 cmatch++;
2746                 if (cmatch == strlen(cont_seq))
2747                 {
2748                     cmatch = 0; // complete match.  just reset the counter
2749
2750                     /*
2751                         it's possible for the ICS to not include the space
2752                         at the end of the last word, making our [correct]
2753                         join operation fuse two separate words.  the server
2754                         does this when the space occurs at the width setting.
2755                     */
2756                     if (!buf_len || buf[buf_len-1] != ' ')
2757                     {
2758                         *bp++ = ' ';
2759                         buf_len++;
2760                     }
2761                 }
2762                 continue;
2763             }
2764             else if (cmatch)
2765             {
2766                 /*
2767                     match failed, so we have to copy what matched before
2768                     falling through and copying this character.  In reality,
2769                     this will only ever be just the newline character, but
2770                     it doesn't hurt to be precise.
2771                 */
2772                 strncpy(bp, cont_seq, cmatch);
2773                 bp += cmatch;
2774                 buf_len += cmatch;
2775                 cmatch = 0;
2776             }
2777         }
2778
2779         // copy this char
2780         *bp++ = data[i];
2781         buf_len++;
2782     }
2783
2784         buf[buf_len] = NULLCHAR;
2785 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2786         next_out = 0;
2787         leftover_start = 0;
2788
2789         i = 0;
2790         while (i < buf_len) {
2791             /* Deal with part of the TELNET option negotiation
2792                protocol.  We refuse to do anything beyond the
2793                defaults, except that we allow the WILL ECHO option,
2794                which ICS uses to turn off password echoing when we are
2795                directly connected to it.  We reject this option
2796                if localLineEditing mode is on (always on in xboard)
2797                and we are talking to port 23, which might be a real
2798                telnet server that will try to keep WILL ECHO on permanently.
2799              */
2800             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2801                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2802                 unsigned char option;
2803                 oldi = i;
2804                 switch ((unsigned char) buf[++i]) {
2805                   case TN_WILL:
2806                     if (appData.debugMode)
2807                       fprintf(debugFP, "\n<WILL ");
2808                     switch (option = (unsigned char) buf[++i]) {
2809                       case TN_ECHO:
2810                         if (appData.debugMode)
2811                           fprintf(debugFP, "ECHO ");
2812                         /* Reply only if this is a change, according
2813                            to the protocol rules. */
2814                         if (remoteEchoOption) break;
2815                         if (appData.localLineEditing &&
2816                             atoi(appData.icsPort) == TN_PORT) {
2817                             TelnetRequest(TN_DONT, TN_ECHO);
2818                         } else {
2819                             EchoOff();
2820                             TelnetRequest(TN_DO, TN_ECHO);
2821                             remoteEchoOption = TRUE;
2822                         }
2823                         break;
2824                       default:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "%d ", option);
2827                         /* Whatever this is, we don't want it. */
2828                         TelnetRequest(TN_DONT, option);
2829                         break;
2830                     }
2831                     break;
2832                   case TN_WONT:
2833                     if (appData.debugMode)
2834                       fprintf(debugFP, "\n<WONT ");
2835                     switch (option = (unsigned char) buf[++i]) {
2836                       case TN_ECHO:
2837                         if (appData.debugMode)
2838                           fprintf(debugFP, "ECHO ");
2839                         /* Reply only if this is a change, according
2840                            to the protocol rules. */
2841                         if (!remoteEchoOption) break;
2842                         EchoOn();
2843                         TelnetRequest(TN_DONT, TN_ECHO);
2844                         remoteEchoOption = FALSE;
2845                         break;
2846                       default:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "%d ", (unsigned char) option);
2849                         /* Whatever this is, it must already be turned
2850                            off, because we never agree to turn on
2851                            anything non-default, so according to the
2852                            protocol rules, we don't reply. */
2853                         break;
2854                     }
2855                     break;
2856                   case TN_DO:
2857                     if (appData.debugMode)
2858                       fprintf(debugFP, "\n<DO ");
2859                     switch (option = (unsigned char) buf[++i]) {
2860                       default:
2861                         /* Whatever this is, we refuse to do it. */
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", option);
2864                         TelnetRequest(TN_WONT, option);
2865                         break;
2866                     }
2867                     break;
2868                   case TN_DONT:
2869                     if (appData.debugMode)
2870                       fprintf(debugFP, "\n<DONT ");
2871                     switch (option = (unsigned char) buf[++i]) {
2872                       default:
2873                         if (appData.debugMode)
2874                           fprintf(debugFP, "%d ", option);
2875                         /* Whatever this is, we are already not doing
2876                            it, because we never agree to do anything
2877                            non-default, so according to the protocol
2878                            rules, we don't reply. */
2879                         break;
2880                     }
2881                     break;
2882                   case TN_IAC:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<IAC ");
2885                     /* Doubled IAC; pass it through */
2886                     i--;
2887                     break;
2888                   default:
2889                     if (appData.debugMode)
2890                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2891                     /* Drop all other telnet commands on the floor */
2892                     break;
2893                 }
2894                 if (oldi > next_out)
2895                   SendToPlayer(&buf[next_out], oldi - next_out);
2896                 if (++i > next_out)
2897                   next_out = i;
2898                 continue;
2899             }
2900
2901             /* OK, this at least will *usually* work */
2902             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2903                 loggedOn = TRUE;
2904             }
2905
2906             if (loggedOn && !intfSet) {
2907                 if (ics_type == ICS_ICC) {
2908                   snprintf(str, MSG_SIZ,
2909                           "/set-quietly interface %s\n/set-quietly style 12\n",
2910                           programVersion);
2911                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2912                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2913                 } else if (ics_type == ICS_CHESSNET) {
2914                   snprintf(str, MSG_SIZ, "/style 12\n");
2915                 } else {
2916                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2917                   strcat(str, programVersion);
2918                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2919                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2920                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2921 #ifdef WIN32
2922                   strcat(str, "$iset nohighlight 1\n");
2923 #endif
2924                   strcat(str, "$iset lock 1\n$style 12\n");
2925                 }
2926                 SendToICS(str);
2927                 NotifyFrontendLogin();
2928                 intfSet = TRUE;
2929             }
2930
2931             if (started == STARTED_COMMENT) {
2932                 /* Accumulate characters in comment */
2933                 parse[parse_pos++] = buf[i];
2934                 if (buf[i] == '\n') {
2935                     parse[parse_pos] = NULLCHAR;
2936                     if(chattingPartner>=0) {
2937                         char mess[MSG_SIZ];
2938                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2939                         OutputChatMessage(chattingPartner, mess);
2940                         chattingPartner = -1;
2941                         next_out = i+1; // [HGM] suppress printing in ICS window
2942                     } else
2943                     if(!suppressKibitz) // [HGM] kibitz
2944                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2945                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2946                         int nrDigit = 0, nrAlph = 0, j;
2947                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2948                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2949                         parse[parse_pos] = NULLCHAR;
2950                         // try to be smart: if it does not look like search info, it should go to
2951                         // ICS interaction window after all, not to engine-output window.
2952                         for(j=0; j<parse_pos; j++) { // count letters and digits
2953                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2954                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2955                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2956                         }
2957                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2958                             int depth=0; float score;
2959                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2960                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2961                                 pvInfoList[forwardMostMove-1].depth = depth;
2962                                 pvInfoList[forwardMostMove-1].score = 100*score;
2963                             }
2964                             OutputKibitz(suppressKibitz, parse);
2965                         } else {
2966                             char tmp[MSG_SIZ];
2967                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2968                             SendToPlayer(tmp, strlen(tmp));
2969                         }
2970                         next_out = i+1; // [HGM] suppress printing in ICS window
2971                     }
2972                     started = STARTED_NONE;
2973                 } else {
2974                     /* Don't match patterns against characters in comment */
2975                     i++;
2976                     continue;
2977                 }
2978             }
2979             if (started == STARTED_CHATTER) {
2980                 if (buf[i] != '\n') {
2981                     /* Don't match patterns against characters in chatter */
2982                     i++;
2983                     continue;
2984                 }
2985                 started = STARTED_NONE;
2986                 if(suppressKibitz) next_out = i+1;
2987             }
2988
2989             /* Kludge to deal with rcmd protocol */
2990             if (firstTime && looking_at(buf, &i, "\001*")) {
2991                 DisplayFatalError(&buf[1], 0, 1);
2992                 continue;
2993             } else {
2994                 firstTime = FALSE;
2995             }
2996
2997             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2998                 ics_type = ICS_ICC;
2999                 ics_prefix = "/";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3005                 ics_type = ICS_FICS;
3006                 ics_prefix = "$";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3012                 ics_type = ICS_CHESSNET;
3013                 ics_prefix = "/";
3014                 if (appData.debugMode)
3015                   fprintf(debugFP, "ics_type %d\n", ics_type);
3016                 continue;
3017             }
3018
3019             if (!loggedOn &&
3020                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3021                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3022                  looking_at(buf, &i, "will be \"*\""))) {
3023               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3024               continue;
3025             }
3026
3027             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3028               char buf[MSG_SIZ];
3029               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3030               DisplayIcsInteractionTitle(buf);
3031               have_set_title = TRUE;
3032             }
3033
3034             /* skip finger notes */
3035             if (started == STARTED_NONE &&
3036                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3037                  (buf[i] == '1' && buf[i+1] == '0')) &&
3038                 buf[i+2] == ':' && buf[i+3] == ' ') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             oldi = i;
3045             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3046             if(appData.seekGraph) {
3047                 if(soughtPending && MatchSoughtLine(buf+i)) {
3048                     i = strstr(buf+i, "rated") - buf;
3049                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                     next_out = leftover_start = i;
3051                     started = STARTED_CHATTER;
3052                     suppressKibitz = TRUE;
3053                     continue;
3054                 }
3055                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3056                         && looking_at(buf, &i, "* ads displayed")) {
3057                     soughtPending = FALSE;
3058                     seekGraphUp = TRUE;
3059                     DrawSeekGraph();
3060                     continue;
3061                 }
3062                 if(appData.autoRefresh) {
3063                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3064                         int s = (ics_type == ICS_ICC); // ICC format differs
3065                         if(seekGraphUp)
3066                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3067                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3068                         looking_at(buf, &i, "*% "); // eat prompt
3069                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3070                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3071                         next_out = i; // suppress
3072                         continue;
3073                     }
3074                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3075                         char *p = star_match[0];
3076                         while(*p) {
3077                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3078                             while(*p && *p++ != ' '); // next
3079                         }
3080                         looking_at(buf, &i, "*% "); // eat prompt
3081                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = i;
3083                         continue;
3084                     }
3085                 }
3086             }
3087
3088             /* skip formula vars */
3089             if (started == STARTED_NONE &&
3090                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3091               started = STARTED_CHATTER;
3092               i += 3;
3093               continue;
3094             }
3095
3096             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3097             if (appData.autoKibitz && started == STARTED_NONE &&
3098                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3099                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3100                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3101                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3102                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3103                         suppressKibitz = TRUE;
3104                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105                         next_out = i;
3106                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3107                                 && (gameMode == IcsPlayingWhite)) ||
3108                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3109                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3110                             started = STARTED_CHATTER; // own kibitz we simply discard
3111                         else {
3112                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3113                             parse_pos = 0; parse[0] = NULLCHAR;
3114                             savingComment = TRUE;
3115                             suppressKibitz = gameMode != IcsObserving ? 2 :
3116                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3117                         }
3118                         continue;
3119                 } else
3120                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3121                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3122                          && atoi(star_match[0])) {
3123                     // suppress the acknowledgements of our own autoKibitz
3124                     char *p;
3125                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3126                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3127                     SendToPlayer(star_match[0], strlen(star_match[0]));
3128                     if(looking_at(buf, &i, "*% ")) // eat prompt
3129                         suppressKibitz = FALSE;
3130                     next_out = i;
3131                     continue;
3132                 }
3133             } // [HGM] kibitz: end of patch
3134
3135             // [HGM] chat: intercept tells by users for which we have an open chat window
3136             channel = -1;
3137             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3138                                            looking_at(buf, &i, "* whispers:") ||
3139                                            looking_at(buf, &i, "* kibitzes:") ||
3140                                            looking_at(buf, &i, "* shouts:") ||
3141                                            looking_at(buf, &i, "* c-shouts:") ||
3142                                            looking_at(buf, &i, "--> * ") ||
3143                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3144                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3147                 int p;
3148                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3149                 chattingPartner = -1;
3150
3151                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3152                 for(p=0; p<MAX_CHAT; p++) {
3153                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3154                     talker[0] = '['; strcat(talker, "] ");
3155                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3156                     chattingPartner = p; break;
3157                     }
3158                 } else
3159                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3160                 for(p=0; p<MAX_CHAT; p++) {
3161                     if(!strcmp("kibitzes", chatPartner[p])) {
3162                         talker[0] = '['; strcat(talker, "] ");
3163                         chattingPartner = p; break;
3164                     }
3165                 } else
3166                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3167                 for(p=0; p<MAX_CHAT; p++) {
3168                     if(!strcmp("whispers", chatPartner[p])) {
3169                         talker[0] = '['; strcat(talker, "] ");
3170                         chattingPartner = p; break;
3171                     }
3172                 } else
3173                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3174                   if(buf[i-8] == '-' && buf[i-3] == 't')
3175                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3176                     if(!strcmp("c-shouts", chatPartner[p])) {
3177                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3178                         chattingPartner = p; break;
3179                     }
3180                   }
3181                   if(chattingPartner < 0)
3182                   for(p=0; p<MAX_CHAT; p++) {
3183                     if(!strcmp("shouts", chatPartner[p])) {
3184                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3185                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3186                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3187                         chattingPartner = p; break;
3188                     }
3189                   }
3190                 }
3191                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3192                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3193                     talker[0] = 0; Colorize(ColorTell, FALSE);
3194                     chattingPartner = p; break;
3195                 }
3196                 if(chattingPartner<0) i = oldi; else {
3197                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3198                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3199                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200                     started = STARTED_COMMENT;
3201                     parse_pos = 0; parse[0] = NULLCHAR;
3202                     savingComment = 3 + chattingPartner; // counts as TRUE
3203                     suppressKibitz = TRUE;
3204                     continue;
3205                 }
3206             } // [HGM] chat: end of patch
3207
3208           backup = i;
3209             if (appData.zippyTalk || appData.zippyPlay) {
3210                 /* [DM] Backup address for color zippy lines */
3211 #if ZIPPY
3212                if (loggedOn == TRUE)
3213                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3214                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3215 #endif
3216             } // [DM] 'else { ' deleted
3217                 if (
3218                     /* Regular tells and says */
3219                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3220                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3221                     looking_at(buf, &i, "* says: ") ||
3222                     /* Don't color "message" or "messages" output */
3223                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3224                     looking_at(buf, &i, "*. * at *:*: ") ||
3225                     looking_at(buf, &i, "--* (*:*): ") ||
3226                     /* Message notifications (same color as tells) */
3227                     looking_at(buf, &i, "* has left a message ") ||
3228                     looking_at(buf, &i, "* just sent you a message:\n") ||
3229                     /* Whispers and kibitzes */
3230                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3231                     looking_at(buf, &i, "* kibitzes: ") ||
3232                     /* Channel tells */
3233                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3234
3235                   if (tkind == 1 && strchr(star_match[0], ':')) {
3236                       /* Avoid "tells you:" spoofs in channels */
3237                      tkind = 3;
3238                   }
3239                   if (star_match[0][0] == NULLCHAR ||
3240                       strchr(star_match[0], ' ') ||
3241                       (tkind == 3 && strchr(star_match[1], ' '))) {
3242                     /* Reject bogus matches */
3243                     i = oldi;
3244                   } else {
3245                     if (appData.colorize) {
3246                       if (oldi > next_out) {
3247                         SendToPlayer(&buf[next_out], oldi - next_out);
3248                         next_out = oldi;
3249                       }
3250                       switch (tkind) {
3251                       case 1:
3252                         Colorize(ColorTell, FALSE);
3253                         curColor = ColorTell;
3254                         break;
3255                       case 2:
3256                         Colorize(ColorKibitz, FALSE);
3257                         curColor = ColorKibitz;
3258                         break;
3259                       case 3:
3260                         p = strrchr(star_match[1], '(');
3261                         if (p == NULL) {
3262                           p = star_match[1];
3263                         } else {
3264                           p++;
3265                         }
3266                         if (atoi(p) == 1) {
3267                           Colorize(ColorChannel1, FALSE);
3268                           curColor = ColorChannel1;
3269                         } else {
3270                           Colorize(ColorChannel, FALSE);
3271                           curColor = ColorChannel;
3272                         }
3273                         break;
3274                       case 5:
3275                         curColor = ColorNormal;
3276                         break;
3277                       }
3278                     }
3279                     if (started == STARTED_NONE && appData.autoComment &&
3280                         (gameMode == IcsObserving ||
3281                          gameMode == IcsPlayingWhite ||
3282                          gameMode == IcsPlayingBlack)) {
3283                       parse_pos = i - oldi;
3284                       memcpy(parse, &buf[oldi], parse_pos);
3285                       parse[parse_pos] = NULLCHAR;
3286                       started = STARTED_COMMENT;
3287                       savingComment = TRUE;
3288                     } else {
3289                       started = STARTED_CHATTER;
3290                       savingComment = FALSE;
3291                     }
3292                     loggedOn = TRUE;
3293                     continue;
3294                   }
3295                 }
3296
3297                 if (looking_at(buf, &i, "* s-shouts: ") ||
3298                     looking_at(buf, &i, "* c-shouts: ")) {
3299                     if (appData.colorize) {
3300                         if (oldi > next_out) {
3301                             SendToPlayer(&buf[next_out], oldi - next_out);
3302                             next_out = oldi;
3303                         }
3304                         Colorize(ColorSShout, FALSE);
3305                         curColor = ColorSShout;
3306                     }
3307                     loggedOn = TRUE;
3308                     started = STARTED_CHATTER;
3309                     continue;
3310                 }
3311
3312                 if (looking_at(buf, &i, "--->")) {
3313                     loggedOn = TRUE;
3314                     continue;
3315                 }
3316
3317                 if (looking_at(buf, &i, "* shouts: ") ||
3318                     looking_at(buf, &i, "--> ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorShout, FALSE);
3325                         curColor = ColorShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at( buf, &i, "Challenge:")) {
3333                     if (appData.colorize) {
3334                         if (oldi > next_out) {
3335                             SendToPlayer(&buf[next_out], oldi - next_out);
3336                             next_out = oldi;
3337                         }
3338                         Colorize(ColorChallenge, FALSE);
3339                         curColor = ColorChallenge;
3340                     }
3341                     loggedOn = TRUE;
3342                     continue;
3343                 }
3344
3345                 if (looking_at(buf, &i, "* offers you") ||
3346                     looking_at(buf, &i, "* offers to be") ||
3347                     looking_at(buf, &i, "* would like to") ||
3348                     looking_at(buf, &i, "* requests to") ||
3349                     looking_at(buf, &i, "Your opponent offers") ||
3350                     looking_at(buf, &i, "Your opponent requests")) {
3351
3352                     if (appData.colorize) {
3353                         if (oldi > next_out) {
3354                             SendToPlayer(&buf[next_out], oldi - next_out);
3355                             next_out = oldi;
3356                         }
3357                         Colorize(ColorRequest, FALSE);
3358                         curColor = ColorRequest;
3359                     }
3360                     continue;
3361                 }
3362
3363                 if (looking_at(buf, &i, "* (*) seeking")) {
3364                     if (appData.colorize) {
3365                         if (oldi > next_out) {
3366                             SendToPlayer(&buf[next_out], oldi - next_out);
3367                             next_out = oldi;
3368                         }
3369                         Colorize(ColorSeek, FALSE);
3370                         curColor = ColorSeek;
3371                     }
3372                     continue;
3373             }
3374
3375           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3376
3377             if (looking_at(buf, &i, "\\   ")) {
3378                 if (prevColor != ColorNormal) {
3379                     if (oldi > next_out) {
3380                         SendToPlayer(&buf[next_out], oldi - next_out);
3381                         next_out = oldi;
3382                     }
3383                     Colorize(prevColor, TRUE);
3384                     curColor = prevColor;
3385                 }
3386                 if (savingComment) {
3387                     parse_pos = i - oldi;
3388                     memcpy(parse, &buf[oldi], parse_pos);
3389                     parse[parse_pos] = NULLCHAR;
3390                     started = STARTED_COMMENT;
3391                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3392                         chattingPartner = savingComment - 3; // kludge to remember the box
3393                 } else {
3394                     started = STARTED_CHATTER;
3395                 }
3396                 continue;
3397             }
3398
3399             if (looking_at(buf, &i, "Black Strength :") ||
3400                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3401                 looking_at(buf, &i, "<10>") ||
3402                 looking_at(buf, &i, "#@#")) {
3403                 /* Wrong board style */
3404                 loggedOn = TRUE;
3405                 SendToICS(ics_prefix);
3406                 SendToICS("set style 12\n");
3407                 SendToICS(ics_prefix);
3408                 SendToICS("refresh\n");
3409                 continue;
3410             }
3411
3412             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3413                 ICSInitScript();
3414                 have_sent_ICS_logon = 1;
3415                 continue;
3416             }
3417
3418             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3419                 (looking_at(buf, &i, "\n<12> ") ||
3420                  looking_at(buf, &i, "<12> "))) {
3421                 loggedOn = TRUE;
3422                 if (oldi > next_out) {
3423                     SendToPlayer(&buf[next_out], oldi - next_out);
3424                 }
3425                 next_out = i;
3426                 started = STARTED_BOARD;
3427                 parse_pos = 0;
3428                 continue;
3429             }
3430
3431             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3432                 looking_at(buf, &i, "<b1> ")) {
3433                 if (oldi > next_out) {
3434                     SendToPlayer(&buf[next_out], oldi - next_out);
3435                 }
3436                 next_out = i;
3437                 started = STARTED_HOLDINGS;
3438                 parse_pos = 0;
3439                 continue;
3440             }
3441
3442             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3443                 loggedOn = TRUE;
3444                 /* Header for a move list -- first line */
3445
3446                 switch (ics_getting_history) {
3447                   case H_FALSE:
3448                     switch (gameMode) {
3449                       case IcsIdle:
3450                       case BeginningOfGame:
3451                         /* User typed "moves" or "oldmoves" while we
3452                            were idle.  Pretend we asked for these
3453                            moves and soak them up so user can step
3454                            through them and/or save them.
3455                            */
3456                         Reset(FALSE, TRUE);
3457                         gameMode = IcsObserving;
3458                         ModeHighlight();
3459                         ics_gamenum = -1;
3460                         ics_getting_history = H_GOT_UNREQ_HEADER;
3461                         break;
3462                       case EditGame: /*?*/
3463                       case EditPosition: /*?*/
3464                         /* Should above feature work in these modes too? */
3465                         /* For now it doesn't */
3466                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3467                         break;
3468                       default:
3469                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3470                         break;
3471                     }
3472                     break;
3473                   case H_REQUESTED:
3474                     /* Is this the right one? */
3475                     if (gameInfo.white && gameInfo.black &&
3476                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3477                         strcmp(gameInfo.black, star_match[2]) == 0) {
3478                         /* All is well */
3479                         ics_getting_history = H_GOT_REQ_HEADER;
3480                     }
3481                     break;
3482                   case H_GOT_REQ_HEADER:
3483                   case H_GOT_UNREQ_HEADER:
3484                   case H_GOT_UNWANTED_HEADER:
3485                   case H_GETTING_MOVES:
3486                     /* Should not happen */
3487                     DisplayError(_("Error gathering move list: two headers"), 0);
3488                     ics_getting_history = H_FALSE;
3489                     break;
3490                 }
3491
3492                 /* Save player ratings into gameInfo if needed */
3493                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3494                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3495                     (gameInfo.whiteRating == -1 ||
3496                      gameInfo.blackRating == -1)) {
3497
3498                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3499                     gameInfo.blackRating = string_to_rating(star_match[3]);
3500                     if (appData.debugMode)
3501                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3502                               gameInfo.whiteRating, gameInfo.blackRating);
3503                 }
3504                 continue;
3505             }
3506
3507             if (looking_at(buf, &i,
3508               "* * match, initial time: * minute*, increment: * second")) {
3509                 /* Header for a move list -- second line */
3510                 /* Initial board will follow if this is a wild game */
3511                 if (gameInfo.event != NULL) free(gameInfo.event);
3512                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3513                 gameInfo.event = StrSave(str);
3514                 /* [HGM] we switched variant. Translate boards if needed. */
3515                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3516                 continue;
3517             }
3518
3519             if (looking_at(buf, &i, "Move  ")) {
3520                 /* Beginning of a move list */
3521                 switch (ics_getting_history) {
3522                   case H_FALSE:
3523                     /* Normally should not happen */
3524                     /* Maybe user hit reset while we were parsing */
3525                     break;
3526                   case H_REQUESTED:
3527                     /* Happens if we are ignoring a move list that is not
3528                      * the one we just requested.  Common if the user
3529                      * tries to observe two games without turning off
3530                      * getMoveList */
3531                     break;
3532                   case H_GETTING_MOVES:
3533                     /* Should not happen */
3534                     DisplayError(_("Error gathering move list: nested"), 0);
3535                     ics_getting_history = H_FALSE;
3536                     break;
3537                   case H_GOT_REQ_HEADER:
3538                     ics_getting_history = H_GETTING_MOVES;
3539                     started = STARTED_MOVES;
3540                     parse_pos = 0;
3541                     if (oldi > next_out) {
3542                         SendToPlayer(&buf[next_out], oldi - next_out);
3543                     }
3544                     break;
3545                   case H_GOT_UNREQ_HEADER:
3546                     ics_getting_history = H_GETTING_MOVES;
3547                     started = STARTED_MOVES_NOHIDE;
3548                     parse_pos = 0;
3549                     break;
3550                   case H_GOT_UNWANTED_HEADER:
3551                     ics_getting_history = H_FALSE;
3552                     break;
3553                 }
3554                 continue;
3555             }
3556
3557             if (looking_at(buf, &i, "% ") ||
3558                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3559                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3560                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3561                     soughtPending = FALSE;
3562                     seekGraphUp = TRUE;
3563                     DrawSeekGraph();
3564                 }
3565                 if(suppressKibitz) next_out = i;
3566                 savingComment = FALSE;
3567                 suppressKibitz = 0;
3568                 switch (started) {
3569                   case STARTED_MOVES:
3570                   case STARTED_MOVES_NOHIDE:
3571                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3572                     parse[parse_pos + i - oldi] = NULLCHAR;
3573                     ParseGameHistory(parse);
3574 #if ZIPPY
3575                     if (appData.zippyPlay && first.initDone) {
3576                         FeedMovesToProgram(&first, forwardMostMove);
3577                         if (gameMode == IcsPlayingWhite) {
3578                             if (WhiteOnMove(forwardMostMove)) {
3579                                 if (first.sendTime) {
3580                                   if (first.useColors) {
3581                                     SendToProgram("black\n", &first);
3582                                   }
3583                                   SendTimeRemaining(&first, TRUE);
3584                                 }
3585                                 if (first.useColors) {
3586                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3587                                 }
3588                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3589                                 first.maybeThinking = TRUE;
3590                             } else {
3591                                 if (first.usePlayother) {
3592                                   if (first.sendTime) {
3593                                     SendTimeRemaining(&first, TRUE);
3594                                   }
3595                                   SendToProgram("playother\n", &first);
3596                                   firstMove = FALSE;
3597                                 } else {
3598                                   firstMove = TRUE;
3599                                 }
3600                             }
3601                         } else if (gameMode == IcsPlayingBlack) {
3602                             if (!WhiteOnMove(forwardMostMove)) {
3603                                 if (first.sendTime) {
3604                                   if (first.useColors) {
3605                                     SendToProgram("white\n", &first);
3606                                   }
3607                                   SendTimeRemaining(&first, FALSE);
3608                                 }
3609                                 if (first.useColors) {
3610                                   SendToProgram("black\n", &first);
3611                                 }
3612                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3613                                 first.maybeThinking = TRUE;
3614                             } else {
3615                                 if (first.usePlayother) {
3616                                   if (first.sendTime) {
3617                                     SendTimeRemaining(&first, FALSE);
3618                                   }
3619                                   SendToProgram("playother\n", &first);
3620                                   firstMove = FALSE;
3621                                 } else {
3622                                   firstMove = TRUE;
3623                                 }
3624                             }
3625                         }
3626                     }
3627 #endif
3628                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3629                         /* Moves came from oldmoves or moves command
3630                            while we weren't doing anything else.
3631                            */
3632                         currentMove = forwardMostMove;
3633                         ClearHighlights();/*!!could figure this out*/
3634                         flipView = appData.flipView;
3635                         DrawPosition(TRUE, boards[currentMove]);
3636                         DisplayBothClocks();
3637                         snprintf(str, MSG_SIZ, "%s vs. %s",
3638                                 gameInfo.white, gameInfo.black);
3639                         DisplayTitle(str);
3640                         gameMode = IcsIdle;
3641                     } else {
3642                         /* Moves were history of an active game */
3643                         if (gameInfo.resultDetails != NULL) {
3644                             free(gameInfo.resultDetails);
3645                             gameInfo.resultDetails = NULL;
3646                         }
3647                     }
3648                     HistorySet(parseList, backwardMostMove,
3649                                forwardMostMove, currentMove-1);
3650                     DisplayMove(currentMove - 1);
3651                     if (started == STARTED_MOVES) next_out = i;
3652                     started = STARTED_NONE;
3653                     ics_getting_history = H_FALSE;
3654                     break;
3655
3656                   case STARTED_OBSERVE:
3657                     started = STARTED_NONE;
3658                     SendToICS(ics_prefix);
3659                     SendToICS("refresh\n");
3660                     break;
3661
3662                   default:
3663                     break;
3664                 }
3665                 if(bookHit) { // [HGM] book: simulate book reply
3666                     static char bookMove[MSG_SIZ]; // a bit generous?
3667
3668                     programStats.nodes = programStats.depth = programStats.time =
3669                     programStats.score = programStats.got_only_move = 0;
3670                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3671
3672                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3673                     strcat(bookMove, bookHit);
3674                     HandleMachineMove(bookMove, &first);
3675                 }
3676                 continue;
3677             }
3678
3679             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3680                  started == STARTED_HOLDINGS ||
3681                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3682                 /* Accumulate characters in move list or board */
3683                 parse[parse_pos++] = buf[i];
3684             }
3685
3686             /* Start of game messages.  Mostly we detect start of game
3687                when the first board image arrives.  On some versions
3688                of the ICS, though, we need to do a "refresh" after starting
3689                to observe in order to get the current board right away. */
3690             if (looking_at(buf, &i, "Adding game * to observation list")) {
3691                 started = STARTED_OBSERVE;
3692                 continue;
3693             }
3694
3695             /* Handle auto-observe */
3696             if (appData.autoObserve &&
3697                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3698                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3699                 char *player;
3700                 /* Choose the player that was highlighted, if any. */
3701                 if (star_match[0][0] == '\033' ||
3702                     star_match[1][0] != '\033') {
3703                     player = star_match[0];
3704                 } else {
3705                     player = star_match[2];
3706                 }
3707                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3708                         ics_prefix, StripHighlightAndTitle(player));
3709                 SendToICS(str);
3710
3711                 /* Save ratings from notify string */
3712                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3713                 player1Rating = string_to_rating(star_match[1]);
3714                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3715                 player2Rating = string_to_rating(star_match[3]);
3716
3717                 if (appData.debugMode)
3718                   fprintf(debugFP,
3719                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3720                           player1Name, player1Rating,
3721                           player2Name, player2Rating);
3722
3723                 continue;
3724             }
3725
3726             /* Deal with automatic examine mode after a game,
3727                and with IcsObserving -> IcsExamining transition */
3728             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3729                 looking_at(buf, &i, "has made you an examiner of game *")) {
3730
3731                 int gamenum = atoi(star_match[0]);
3732                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3733                     gamenum == ics_gamenum) {
3734                     /* We were already playing or observing this game;
3735                        no need to refetch history */
3736                     gameMode = IcsExamining;
3737                     if (pausing) {
3738                         pauseExamForwardMostMove = forwardMostMove;
3739                     } else if (currentMove < forwardMostMove) {
3740                         ForwardInner(forwardMostMove);
3741                     }
3742                 } else {
3743                     /* I don't think this case really can happen */
3744                     SendToICS(ics_prefix);
3745                     SendToICS("refresh\n");
3746                 }
3747                 continue;
3748             }
3749
3750             /* Error messages */
3751 //          if (ics_user_moved) {
3752             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3753                 if (looking_at(buf, &i, "Illegal move") ||
3754                     looking_at(buf, &i, "Not a legal move") ||
3755                     looking_at(buf, &i, "Your king is in check") ||
3756                     looking_at(buf, &i, "It isn't your turn") ||
3757                     looking_at(buf, &i, "It is not your move")) {
3758                     /* Illegal move */
3759                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3760                         currentMove = forwardMostMove-1;
3761                         DisplayMove(currentMove - 1); /* before DMError */
3762                         DrawPosition(FALSE, boards[currentMove]);
3763                         SwitchClocks(forwardMostMove-1); // [HGM] race
3764                         DisplayBothClocks();
3765                     }
3766                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3767                     ics_user_moved = 0;
3768                     continue;
3769                 }
3770             }
3771
3772             if (looking_at(buf, &i, "still have time") ||
3773                 looking_at(buf, &i, "not out of time") ||
3774                 looking_at(buf, &i, "either player is out of time") ||
3775                 looking_at(buf, &i, "has timeseal; checking")) {
3776                 /* We must have called his flag a little too soon */
3777                 whiteFlag = blackFlag = FALSE;
3778                 continue;
3779             }
3780
3781             if (looking_at(buf, &i, "added * seconds to") ||
3782                 looking_at(buf, &i, "seconds were added to")) {
3783                 /* Update the clocks */
3784                 SendToICS(ics_prefix);
3785                 SendToICS("refresh\n");
3786                 continue;
3787             }
3788
3789             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3790                 ics_clock_paused = TRUE;
3791                 StopClocks();
3792                 continue;
3793             }
3794
3795             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3796                 ics_clock_paused = FALSE;
3797                 StartClocks();
3798                 continue;
3799             }
3800
3801             /* Grab player ratings from the Creating: message.
3802                Note we have to check for the special case when
3803                the ICS inserts things like [white] or [black]. */
3804             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3805                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3806                 /* star_matches:
3807                    0    player 1 name (not necessarily white)
3808                    1    player 1 rating
3809                    2    empty, white, or black (IGNORED)
3810                    3    player 2 name (not necessarily black)
3811                    4    player 2 rating
3812
3813                    The names/ratings are sorted out when the game
3814                    actually starts (below).
3815                 */
3816                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3817                 player1Rating = string_to_rating(star_match[1]);
3818                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3819                 player2Rating = string_to_rating(star_match[4]);
3820
3821                 if (appData.debugMode)
3822                   fprintf(debugFP,
3823                           "Ratings from 'Creating:' %s %d, %s %d\n",
3824                           player1Name, player1Rating,
3825                           player2Name, player2Rating);
3826
3827                 continue;
3828             }
3829
3830             /* Improved generic start/end-of-game messages */
3831             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3832                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3833                 /* If tkind == 0: */
3834                 /* star_match[0] is the game number */
3835                 /*           [1] is the white player's name */
3836                 /*           [2] is the black player's name */
3837                 /* For end-of-game: */
3838                 /*           [3] is the reason for the game end */
3839                 /*           [4] is a PGN end game-token, preceded by " " */
3840                 /* For start-of-game: */
3841                 /*           [3] begins with "Creating" or "Continuing" */
3842                 /*           [4] is " *" or empty (don't care). */
3843                 int gamenum = atoi(star_match[0]);
3844                 char *whitename, *blackname, *why, *endtoken;
3845                 ChessMove endtype = EndOfFile;
3846
3847                 if (tkind == 0) {
3848                   whitename = star_match[1];
3849                   blackname = star_match[2];
3850                   why = star_match[3];
3851                   endtoken = star_match[4];
3852                 } else {
3853                   whitename = star_match[1];
3854                   blackname = star_match[3];
3855                   why = star_match[5];
3856                   endtoken = star_match[6];
3857                 }
3858
3859                 /* Game start messages */
3860                 if (strncmp(why, "Creating ", 9) == 0 ||
3861                     strncmp(why, "Continuing ", 11) == 0) {
3862                     gs_gamenum = gamenum;
3863                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3864                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3865 #if ZIPPY
3866                     if (appData.zippyPlay) {
3867                         ZippyGameStart(whitename, blackname);
3868                     }
3869 #endif /*ZIPPY*/
3870                     partnerBoardValid = FALSE; // [HGM] bughouse
3871                     continue;
3872                 }
3873
3874                 /* Game end messages */
3875                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3876                     ics_gamenum != gamenum) {
3877                     continue;
3878                 }
3879                 while (endtoken[0] == ' ') endtoken++;
3880                 switch (endtoken[0]) {
3881                   case '*':
3882                   default:
3883                     endtype = GameUnfinished;
3884                     break;
3885                   case '0':
3886                     endtype = BlackWins;
3887                     break;
3888                   case '1':
3889                     if (endtoken[1] == '/')
3890                       endtype = GameIsDrawn;
3891                     else
3892                       endtype = WhiteWins;
3893                     break;
3894                 }
3895                 GameEnds(endtype, why, GE_ICS);
3896 #if ZIPPY
3897                 if (appData.zippyPlay && first.initDone) {
3898                     ZippyGameEnd(endtype, why);
3899                     if (first.pr == NULL) {
3900                       /* Start the next process early so that we'll
3901                          be ready for the next challenge */
3902                       StartChessProgram(&first);
3903                     }
3904                     /* Send "new" early, in case this command takes
3905                        a long time to finish, so that we'll be ready
3906                        for the next challenge. */
3907                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3908                     Reset(TRUE, TRUE);
3909                 }
3910 #endif /*ZIPPY*/
3911                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3912                 continue;
3913             }
3914
3915             if (looking_at(buf, &i, "Removing game * from observation") ||
3916                 looking_at(buf, &i, "no longer observing game *") ||
3917                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3918                 if (gameMode == IcsObserving &&
3919                     atoi(star_match[0]) == ics_gamenum)
3920                   {
3921                       /* icsEngineAnalyze */
3922                       if (appData.icsEngineAnalyze) {
3923                             ExitAnalyzeMode();
3924                             ModeHighlight();
3925                       }
3926                       StopClocks();
3927                       gameMode = IcsIdle;
3928                       ics_gamenum = -1;
3929                       ics_user_moved = FALSE;
3930                   }
3931                 continue;
3932             }
3933
3934             if (looking_at(buf, &i, "no longer examining game *")) {
3935                 if (gameMode == IcsExamining &&
3936                     atoi(star_match[0]) == ics_gamenum)
3937                   {
3938                       gameMode = IcsIdle;
3939                       ics_gamenum = -1;
3940                       ics_user_moved = FALSE;
3941                   }
3942                 continue;
3943             }
3944
3945             /* Advance leftover_start past any newlines we find,
3946                so only partial lines can get reparsed */
3947             if (looking_at(buf, &i, "\n")) {
3948                 prevColor = curColor;
3949                 if (curColor != ColorNormal) {
3950                     if (oldi > next_out) {
3951                         SendToPlayer(&buf[next_out], oldi - next_out);
3952                         next_out = oldi;
3953                     }
3954                     Colorize(ColorNormal, FALSE);
3955                     curColor = ColorNormal;
3956                 }
3957                 if (started == STARTED_BOARD) {
3958                     started = STARTED_NONE;
3959                     parse[parse_pos] = NULLCHAR;
3960                     ParseBoard12(parse);
3961                     ics_user_moved = 0;
3962
3963                     /* Send premove here */
3964                     if (appData.premove) {
3965                       char str[MSG_SIZ];
3966                       if (currentMove == 0 &&
3967                           gameMode == IcsPlayingWhite &&
3968                           appData.premoveWhite) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (currentMove == 1 &&
3974                                  gameMode == IcsPlayingBlack &&
3975                                  appData.premoveBlack) {
3976                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3977                         if (appData.debugMode)
3978                           fprintf(debugFP, "Sending premove:\n");
3979                         SendToICS(str);
3980                       } else if (gotPremove) {
3981                         gotPremove = 0;
3982                         ClearPremoveHighlights();
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                           UserMoveEvent(premoveFromX, premoveFromY,
3986                                         premoveToX, premoveToY,
3987                                         premovePromoChar);
3988                       }
3989                     }
3990
3991                     /* Usually suppress following prompt */
3992                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3993                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3994                         if (looking_at(buf, &i, "*% ")) {
3995                             savingComment = FALSE;
3996                             suppressKibitz = 0;
3997                         }
3998                     }
3999                     next_out = i;
4000                 } else if (started == STARTED_HOLDINGS) {
4001                     int gamenum;
4002                     char new_piece[MSG_SIZ];
4003                     started = STARTED_NONE;
4004                     parse[parse_pos] = NULLCHAR;
4005                     if (appData.debugMode)
4006                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4007                                                         parse, currentMove);
4008                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4009                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4010                         if (gameInfo.variant == VariantNormal) {
4011                           /* [HGM] We seem to switch variant during a game!
4012                            * Presumably no holdings were displayed, so we have
4013                            * to move the position two files to the right to
4014                            * create room for them!
4015                            */
4016                           VariantClass newVariant;
4017                           switch(gameInfo.boardWidth) { // base guess on board width
4018                                 case 9:  newVariant = VariantShogi; break;
4019                                 case 10: newVariant = VariantGreat; break;
4020                                 default: newVariant = VariantCrazyhouse; break;
4021                           }
4022                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4023                           /* Get a move list just to see the header, which
4024                              will tell us whether this is really bug or zh */
4025                           if (ics_getting_history == H_FALSE) {
4026                             ics_getting_history = H_REQUESTED;
4027                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4028                             SendToICS(str);
4029                           }
4030                         }
4031                         new_piece[0] = NULLCHAR;
4032                         sscanf(parse, "game %d white [%s black [%s <- %s",
4033                                &gamenum, white_holding, black_holding,
4034                                new_piece);
4035                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4036                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4037                         /* [HGM] copy holdings to board holdings area */
4038                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4039                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4040                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4041 #if ZIPPY
4042                         if (appData.zippyPlay && first.initDone) {
4043                             ZippyHoldings(white_holding, black_holding,
4044                                           new_piece);
4045                         }
4046 #endif /*ZIPPY*/
4047                         if (tinyLayout || smallLayout) {
4048                             char wh[16], bh[16];
4049                             PackHolding(wh, white_holding);
4050                             PackHolding(bh, black_holding);
4051                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4052                                     gameInfo.white, gameInfo.black);
4053                         } else {
4054                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4055                                     gameInfo.white, white_holding,
4056                                     gameInfo.black, black_holding);
4057                         }
4058                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4059                         DrawPosition(FALSE, boards[currentMove]);
4060                         DisplayTitle(str);
4061                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4062                         sscanf(parse, "game %d white [%s black [%s <- %s",
4063                                &gamenum, white_holding, black_holding,
4064                                new_piece);
4065                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4066                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4067                         /* [HGM] copy holdings to partner-board holdings area */
4068                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4069                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4070                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4071                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4072                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4073                       }
4074                     }
4075                     /* Suppress following prompt */
4076                     if (looking_at(buf, &i, "*% ")) {
4077                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4078                         savingComment = FALSE;
4079                         suppressKibitz = 0;
4080                     }
4081                     next_out = i;
4082                 }
4083                 continue;
4084             }
4085
4086             i++;                /* skip unparsed character and loop back */
4087         }
4088
4089         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4090 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4091 //          SendToPlayer(&buf[next_out], i - next_out);
4092             started != STARTED_HOLDINGS && leftover_start > next_out) {
4093             SendToPlayer(&buf[next_out], leftover_start - next_out);
4094             next_out = i;
4095         }
4096
4097         leftover_len = buf_len - leftover_start;
4098         /* if buffer ends with something we couldn't parse,
4099            reparse it after appending the next read */
4100
4101     } else if (count == 0) {
4102         RemoveInputSource(isr);
4103         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4104     } else {
4105         DisplayFatalError(_("Error reading from ICS"), error, 1);
4106     }
4107 }
4108
4109
4110 /* Board style 12 looks like this:
4111
4112    <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
4113
4114  * The "<12> " is stripped before it gets to this routine.  The two
4115  * trailing 0's (flip state and clock ticking) are later addition, and
4116  * some chess servers may not have them, or may have only the first.
4117  * Additional trailing fields may be added in the future.
4118  */
4119
4120 #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"
4121
4122 #define RELATION_OBSERVING_PLAYED    0
4123 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4124 #define RELATION_PLAYING_MYMOVE      1
4125 #define RELATION_PLAYING_NOTMYMOVE  -1
4126 #define RELATION_EXAMINING           2
4127 #define RELATION_ISOLATED_BOARD     -3
4128 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4129
4130 void
4131 ParseBoard12(string)
4132      char *string;
4133 {
4134     GameMode newGameMode;
4135     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4136     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4137     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4138     char to_play, board_chars[200];
4139     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4140     char black[32], white[32];
4141     Board board;
4142     int prevMove = currentMove;
4143     int ticking = 2;
4144     ChessMove moveType;
4145     int fromX, fromY, toX, toY;
4146     char promoChar;
4147     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4148     char *bookHit = NULL; // [HGM] book
4149     Boolean weird = FALSE, reqFlag = FALSE;
4150
4151     fromX = fromY = toX = toY = -1;
4152
4153     newGame = FALSE;
4154
4155     if (appData.debugMode)
4156       fprintf(debugFP, _("Parsing board: %s\n"), string);
4157
4158     move_str[0] = NULLCHAR;
4159     elapsed_time[0] = NULLCHAR;
4160     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4161         int  i = 0, j;
4162         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4163             if(string[i] == ' ') { ranks++; files = 0; }
4164             else files++;
4165             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4166             i++;
4167         }
4168         for(j = 0; j <i; j++) board_chars[j] = string[j];
4169         board_chars[i] = '\0';
4170         string += i + 1;
4171     }
4172     n = sscanf(string, PATTERN, &to_play, &double_push,
4173                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4174                &gamenum, white, black, &relation, &basetime, &increment,
4175                &white_stren, &black_stren, &white_time, &black_time,
4176                &moveNum, str, elapsed_time, move_str, &ics_flip,
4177                &ticking);
4178
4179     if (n < 21) {
4180         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4181         DisplayError(str, 0);
4182         return;
4183     }
4184
4185     /* Convert the move number to internal form */
4186     moveNum = (moveNum - 1) * 2;
4187     if (to_play == 'B') moveNum++;
4188     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4189       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4190                         0, 1);
4191       return;
4192     }
4193
4194     switch (relation) {
4195       case RELATION_OBSERVING_PLAYED:
4196       case RELATION_OBSERVING_STATIC:
4197         if (gamenum == -1) {
4198             /* Old ICC buglet */
4199             relation = RELATION_OBSERVING_STATIC;
4200         }
4201         newGameMode = IcsObserving;
4202         break;
4203       case RELATION_PLAYING_MYMOVE:
4204       case RELATION_PLAYING_NOTMYMOVE:
4205         newGameMode =
4206           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4207             IcsPlayingWhite : IcsPlayingBlack;
4208         break;
4209       case RELATION_EXAMINING:
4210         newGameMode = IcsExamining;
4211         break;
4212       case RELATION_ISOLATED_BOARD:
4213       default:
4214         /* Just display this board.  If user was doing something else,
4215            we will forget about it until the next board comes. */
4216         newGameMode = IcsIdle;
4217         break;
4218       case RELATION_STARTING_POSITION:
4219         newGameMode = gameMode;
4220         break;
4221     }
4222
4223     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4224          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4225       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4226       char *toSqr;
4227       for (k = 0; k < ranks; k++) {
4228         for (j = 0; j < files; j++)
4229           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4230         if(gameInfo.holdingsWidth > 1) {
4231              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4232              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4233         }
4234       }
4235       CopyBoard(partnerBoard, board);
4236       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4237         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4238         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4239       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4240       if(toSqr = strchr(str, '-')) {
4241         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4242         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4243       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4244       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4245       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4246       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4247       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4248       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4249                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4250       DisplayMessage(partnerStatus, "");
4251         partnerBoardValid = TRUE;
4252       return;
4253     }
4254
4255     /* Modify behavior for initial board display on move listing
4256        of wild games.
4257        */
4258     switch (ics_getting_history) {
4259       case H_FALSE:
4260       case H_REQUESTED:
4261         break;
4262       case H_GOT_REQ_HEADER:
4263       case H_GOT_UNREQ_HEADER:
4264         /* This is the initial position of the current game */
4265         gamenum = ics_gamenum;
4266         moveNum = 0;            /* old ICS bug workaround */
4267         if (to_play == 'B') {
4268           startedFromSetupPosition = TRUE;
4269           blackPlaysFirst = TRUE;
4270           moveNum = 1;
4271           if (forwardMostMove == 0) forwardMostMove = 1;
4272           if (backwardMostMove == 0) backwardMostMove = 1;
4273           if (currentMove == 0) currentMove = 1;
4274         }
4275         newGameMode = gameMode;
4276         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4277         break;
4278       case H_GOT_UNWANTED_HEADER:
4279         /* This is an initial board that we don't want */
4280         return;
4281       case H_GETTING_MOVES:
4282         /* Should not happen */
4283         DisplayError(_("Error gathering move list: extra board"), 0);
4284         ics_getting_history = H_FALSE;
4285         return;
4286     }
4287
4288    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4289                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4290      /* [HGM] We seem to have switched variant unexpectedly
4291       * Try to guess new variant from board size
4292       */
4293           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4294           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4295           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4296           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4297           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4298           if(!weird) newVariant = VariantNormal;
4299           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4300           /* Get a move list just to see the header, which
4301              will tell us whether this is really bug or zh */
4302           if (ics_getting_history == H_FALSE) {
4303             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4304             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4305             SendToICS(str);
4306           }
4307     }
4308
4309     /* Take action if this is the first board of a new game, or of a
4310        different game than is currently being displayed.  */
4311     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4312         relation == RELATION_ISOLATED_BOARD) {
4313
4314         /* Forget the old game and get the history (if any) of the new one */
4315         if (gameMode != BeginningOfGame) {
4316           Reset(TRUE, TRUE);
4317         }
4318         newGame = TRUE;
4319         if (appData.autoRaiseBoard) BoardToTop();
4320         prevMove = -3;
4321         if (gamenum == -1) {
4322             newGameMode = IcsIdle;
4323         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4324                    appData.getMoveList && !reqFlag) {
4325             /* Need to get game history */
4326             ics_getting_history = H_REQUESTED;
4327             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4328             SendToICS(str);
4329         }
4330
4331         /* Initially flip the board to have black on the bottom if playing
4332            black or if the ICS flip flag is set, but let the user change
4333            it with the Flip View button. */
4334         flipView = appData.autoFlipView ?
4335           (newGameMode == IcsPlayingBlack) || ics_flip :
4336           appData.flipView;
4337
4338         /* Done with values from previous mode; copy in new ones */
4339         gameMode = newGameMode;
4340         ModeHighlight();
4341         ics_gamenum = gamenum;
4342         if (gamenum == gs_gamenum) {
4343             int klen = strlen(gs_kind);
4344             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4345             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4346             gameInfo.event = StrSave(str);
4347         } else {
4348             gameInfo.event = StrSave("ICS game");
4349         }
4350         gameInfo.site = StrSave(appData.icsHost);
4351         gameInfo.date = PGNDate();
4352         gameInfo.round = StrSave("-");
4353         gameInfo.white = StrSave(white);
4354         gameInfo.black = StrSave(black);
4355         timeControl = basetime * 60 * 1000;
4356         timeControl_2 = 0;
4357         timeIncrement = increment * 1000;
4358         movesPerSession = 0;
4359         gameInfo.timeControl = TimeControlTagValue();
4360         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4361   if (appData.debugMode) {
4362     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4363     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4364     setbuf(debugFP, NULL);
4365   }
4366
4367         gameInfo.outOfBook = NULL;
4368
4369         /* Do we have the ratings? */
4370         if (strcmp(player1Name, white) == 0 &&
4371             strcmp(player2Name, black) == 0) {
4372             if (appData.debugMode)
4373               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4374                       player1Rating, player2Rating);
4375             gameInfo.whiteRating = player1Rating;
4376             gameInfo.blackRating = player2Rating;
4377         } else if (strcmp(player2Name, white) == 0 &&
4378                    strcmp(player1Name, black) == 0) {
4379             if (appData.debugMode)
4380               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4381                       player2Rating, player1Rating);
4382             gameInfo.whiteRating = player2Rating;
4383             gameInfo.blackRating = player1Rating;
4384         }
4385         player1Name[0] = player2Name[0] = NULLCHAR;
4386
4387         /* Silence shouts if requested */
4388         if (appData.quietPlay &&
4389             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4390             SendToICS(ics_prefix);
4391             SendToICS("set shout 0\n");
4392         }
4393     }
4394
4395     /* Deal with midgame name changes */
4396     if (!newGame) {
4397         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4398             if (gameInfo.white) free(gameInfo.white);
4399             gameInfo.white = StrSave(white);
4400         }
4401         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4402             if (gameInfo.black) free(gameInfo.black);
4403             gameInfo.black = StrSave(black);
4404         }
4405     }
4406
4407     /* Throw away game result if anything actually changes in examine mode */
4408     if (gameMode == IcsExamining && !newGame) {
4409         gameInfo.result = GameUnfinished;
4410         if (gameInfo.resultDetails != NULL) {
4411             free(gameInfo.resultDetails);
4412             gameInfo.resultDetails = NULL;
4413         }
4414     }
4415
4416     /* In pausing && IcsExamining mode, we ignore boards coming
4417        in if they are in a different variation than we are. */
4418     if (pauseExamInvalid) return;
4419     if (pausing && gameMode == IcsExamining) {
4420         if (moveNum <= pauseExamForwardMostMove) {
4421             pauseExamInvalid = TRUE;
4422             forwardMostMove = pauseExamForwardMostMove;
4423             return;
4424         }
4425     }
4426
4427   if (appData.debugMode) {
4428     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4429   }
4430     /* Parse the board */
4431     for (k = 0; k < ranks; k++) {
4432       for (j = 0; j < files; j++)
4433         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4434       if(gameInfo.holdingsWidth > 1) {
4435            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4436            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4437       }
4438     }
4439     CopyBoard(boards[moveNum], board);
4440     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4441     if (moveNum == 0) {
4442         startedFromSetupPosition =
4443           !CompareBoards(board, initialPosition);
4444         if(startedFromSetupPosition)
4445             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4446     }
4447
4448     /* [HGM] Set castling rights. Take the outermost Rooks,
4449        to make it also work for FRC opening positions. Note that board12
4450        is really defective for later FRC positions, as it has no way to
4451        indicate which Rook can castle if they are on the same side of King.
4452        For the initial position we grant rights to the outermost Rooks,
4453        and remember thos rights, and we then copy them on positions
4454        later in an FRC game. This means WB might not recognize castlings with
4455        Rooks that have moved back to their original position as illegal,
4456        but in ICS mode that is not its job anyway.
4457     */
4458     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4459     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4460
4461         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4462             if(board[0][i] == WhiteRook) j = i;
4463         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4464         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4465             if(board[0][i] == WhiteRook) j = i;
4466         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4467         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4468             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4469         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4471             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4472         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473
4474         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4475         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4476             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4477         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4478             if(board[BOARD_HEIGHT-1][k] == bKing)
4479                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4480         if(gameInfo.variant == VariantTwoKings) {
4481             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4482             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4483             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4484         }
4485     } else { int r;
4486         r = boards[moveNum][CASTLING][0] = initialRights[0];
4487         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4488         r = boards[moveNum][CASTLING][1] = initialRights[1];
4489         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4490         r = boards[moveNum][CASTLING][3] = initialRights[3];
4491         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4492         r = boards[moveNum][CASTLING][4] = initialRights[4];
4493         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4494         /* wildcastle kludge: always assume King has rights */
4495         r = boards[moveNum][CASTLING][2] = initialRights[2];
4496         r = boards[moveNum][CASTLING][5] = initialRights[5];
4497     }
4498     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4499     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4500
4501
4502     if (ics_getting_history == H_GOT_REQ_HEADER ||
4503         ics_getting_history == H_GOT_UNREQ_HEADER) {
4504         /* This was an initial position from a move list, not
4505            the current position */
4506         return;
4507     }
4508
4509     /* Update currentMove and known move number limits */
4510     newMove = newGame || moveNum > forwardMostMove;
4511
4512     if (newGame) {
4513         forwardMostMove = backwardMostMove = currentMove = moveNum;
4514         if (gameMode == IcsExamining && moveNum == 0) {
4515           /* Workaround for ICS limitation: we are not told the wild
4516              type when starting to examine a game.  But if we ask for
4517              the move list, the move list header will tell us */
4518             ics_getting_history = H_REQUESTED;
4519             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4520             SendToICS(str);
4521         }
4522     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4523                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4524 #if ZIPPY
4525         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4526         /* [HGM] applied this also to an engine that is silently watching        */
4527         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4528             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4529             gameInfo.variant == currentlyInitializedVariant) {
4530           takeback = forwardMostMove - moveNum;
4531           for (i = 0; i < takeback; i++) {
4532             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4533             SendToProgram("undo\n", &first);
4534           }
4535         }
4536 #endif
4537
4538         forwardMostMove = moveNum;
4539         if (!pausing || currentMove > forwardMostMove)
4540           currentMove = forwardMostMove;
4541     } else {
4542         /* New part of history that is not contiguous with old part */
4543         if (pausing && gameMode == IcsExamining) {
4544             pauseExamInvalid = TRUE;
4545             forwardMostMove = pauseExamForwardMostMove;
4546             return;
4547         }
4548         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4549 #if ZIPPY
4550             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4551                 // [HGM] when we will receive the move list we now request, it will be
4552                 // fed to the engine from the first move on. So if the engine is not
4553                 // in the initial position now, bring it there.
4554                 InitChessProgram(&first, 0);
4555             }
4556 #endif
4557             ics_getting_history = H_REQUESTED;
4558             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4559             SendToICS(str);
4560         }
4561         forwardMostMove = backwardMostMove = currentMove = moveNum;
4562     }
4563
4564     /* Update the clocks */
4565     if (strchr(elapsed_time, '.')) {
4566       /* Time is in ms */
4567       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4568       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4569     } else {
4570       /* Time is in seconds */
4571       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4572       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4573     }
4574
4575
4576 #if ZIPPY
4577     if (appData.zippyPlay && newGame &&
4578         gameMode != IcsObserving && gameMode != IcsIdle &&
4579         gameMode != IcsExamining)
4580       ZippyFirstBoard(moveNum, basetime, increment);
4581 #endif
4582
4583     /* Put the move on the move list, first converting
4584        to canonical algebraic form. */
4585     if (moveNum > 0) {
4586   if (appData.debugMode) {
4587     if (appData.debugMode) { int f = forwardMostMove;
4588         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4589                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4590                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4591     }
4592     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4593     fprintf(debugFP, "moveNum = %d\n", moveNum);
4594     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4595     setbuf(debugFP, NULL);
4596   }
4597         if (moveNum <= backwardMostMove) {
4598             /* We don't know what the board looked like before
4599                this move.  Punt. */
4600           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4601             strcat(parseList[moveNum - 1], " ");
4602             strcat(parseList[moveNum - 1], elapsed_time);
4603             moveList[moveNum - 1][0] = NULLCHAR;
4604         } else if (strcmp(move_str, "none") == 0) {
4605             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4606             /* Again, we don't know what the board looked like;
4607                this is really the start of the game. */
4608             parseList[moveNum - 1][0] = NULLCHAR;
4609             moveList[moveNum - 1][0] = NULLCHAR;
4610             backwardMostMove = moveNum;
4611             startedFromSetupPosition = TRUE;
4612             fromX = fromY = toX = toY = -1;
4613         } else {
4614           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4615           //                 So we parse the long-algebraic move string in stead of the SAN move
4616           int valid; char buf[MSG_SIZ], *prom;
4617
4618           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4619                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4620           // str looks something like "Q/a1-a2"; kill the slash
4621           if(str[1] == '/')
4622             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4623           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4624           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4625                 strcat(buf, prom); // long move lacks promo specification!
4626           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4627                 if(appData.debugMode)
4628                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4629                 safeStrCpy(move_str, buf, MSG_SIZ);
4630           }
4631           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4632                                 &fromX, &fromY, &toX, &toY, &promoChar)
4633                || ParseOneMove(buf, moveNum - 1, &moveType,
4634                                 &fromX, &fromY, &toX, &toY, &promoChar);
4635           // end of long SAN patch
4636           if (valid) {
4637             (void) CoordsToAlgebraic(boards[moveNum - 1],
4638                                      PosFlags(moveNum - 1),
4639                                      fromY, fromX, toY, toX, promoChar,
4640                                      parseList[moveNum-1]);
4641             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4642               case MT_NONE:
4643               case MT_STALEMATE:
4644               default:
4645                 break;
4646               case MT_CHECK:
4647                 if(gameInfo.variant != VariantShogi)
4648                     strcat(parseList[moveNum - 1], "+");
4649                 break;
4650               case MT_CHECKMATE:
4651               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4652                 strcat(parseList[moveNum - 1], "#");
4653                 break;
4654             }
4655             strcat(parseList[moveNum - 1], " ");
4656             strcat(parseList[moveNum - 1], elapsed_time);
4657             /* currentMoveString is set as a side-effect of ParseOneMove */
4658             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4659             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4660             strcat(moveList[moveNum - 1], "\n");
4661
4662             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4663                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4664               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4665                 ChessSquare old, new = boards[moveNum][k][j];
4666                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4667                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4668                   if(old == new) continue;
4669                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4670                   else if(new == WhiteWazir || new == BlackWazir) {
4671                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4672                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4673                       else boards[moveNum][k][j] = old; // preserve type of Gold
4674                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4675                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4676               }
4677           } else {
4678             /* Move from ICS was illegal!?  Punt. */
4679             if (appData.debugMode) {
4680               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4681               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4682             }
4683             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4684             strcat(parseList[moveNum - 1], " ");
4685             strcat(parseList[moveNum - 1], elapsed_time);
4686             moveList[moveNum - 1][0] = NULLCHAR;
4687             fromX = fromY = toX = toY = -1;
4688           }
4689         }
4690   if (appData.debugMode) {
4691     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4692     setbuf(debugFP, NULL);
4693   }
4694
4695 #if ZIPPY
4696         /* Send move to chess program (BEFORE animating it). */
4697         if (appData.zippyPlay && !newGame && newMove &&
4698            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4699
4700             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4701                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4702                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4703                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4704                             move_str);
4705                     DisplayError(str, 0);
4706                 } else {
4707                     if (first.sendTime) {
4708                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4709                     }
4710                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4711                     if (firstMove && !bookHit) {
4712                         firstMove = FALSE;
4713                         if (first.useColors) {
4714                           SendToProgram(gameMode == IcsPlayingWhite ?
4715                                         "white\ngo\n" :
4716                                         "black\ngo\n", &first);
4717                         } else {
4718                           SendToProgram("go\n", &first);
4719                         }
4720                         first.maybeThinking = TRUE;
4721                     }
4722                 }
4723             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4724               if (moveList[moveNum - 1][0] == NULLCHAR) {
4725                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4726                 DisplayError(str, 0);
4727               } else {
4728                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4729                 SendMoveToProgram(moveNum - 1, &first);
4730               }
4731             }
4732         }
4733 #endif
4734     }
4735
4736     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4737         /* If move comes from a remote source, animate it.  If it
4738            isn't remote, it will have already been animated. */
4739         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4740             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4741         }
4742         if (!pausing && appData.highlightLastMove) {
4743             SetHighlights(fromX, fromY, toX, toY);
4744         }
4745     }
4746
4747     /* Start the clocks */
4748     whiteFlag = blackFlag = FALSE;
4749     appData.clockMode = !(basetime == 0 && increment == 0);
4750     if (ticking == 0) {
4751       ics_clock_paused = TRUE;
4752       StopClocks();
4753     } else if (ticking == 1) {
4754       ics_clock_paused = FALSE;
4755     }
4756     if (gameMode == IcsIdle ||
4757         relation == RELATION_OBSERVING_STATIC ||
4758         relation == RELATION_EXAMINING ||
4759         ics_clock_paused)
4760       DisplayBothClocks();
4761     else
4762       StartClocks();
4763
4764     /* Display opponents and material strengths */
4765     if (gameInfo.variant != VariantBughouse &&
4766         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4767         if (tinyLayout || smallLayout) {
4768             if(gameInfo.variant == VariantNormal)
4769               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4770                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4771                     basetime, increment);
4772             else
4773               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment, (int) gameInfo.variant);
4776         } else {
4777             if(gameInfo.variant == VariantNormal)
4778               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4779                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4780                     basetime, increment);
4781             else
4782               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4783                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4784                     basetime, increment, VariantName(gameInfo.variant));
4785         }
4786         DisplayTitle(str);
4787   if (appData.debugMode) {
4788     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4789   }
4790     }
4791
4792
4793     /* Display the board */
4794     if (!pausing && !appData.noGUI) {
4795
4796       if (appData.premove)
4797           if (!gotPremove ||
4798              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4799              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4800               ClearPremoveHighlights();
4801
4802       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4803         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4804       DrawPosition(j, boards[currentMove]);
4805
4806       DisplayMove(moveNum - 1);
4807       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4808             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4809               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4810         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4811       }
4812     }
4813
4814     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4815 #if ZIPPY
4816     if(bookHit) { // [HGM] book: simulate book reply
4817         static char bookMove[MSG_SIZ]; // a bit generous?
4818
4819         programStats.nodes = programStats.depth = programStats.time =
4820         programStats.score = programStats.got_only_move = 0;
4821         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4822
4823         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4824         strcat(bookMove, bookHit);
4825         HandleMachineMove(bookMove, &first);
4826     }
4827 #endif
4828 }
4829
4830 void
4831 GetMoveListEvent()
4832 {
4833     char buf[MSG_SIZ];
4834     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4835         ics_getting_history = H_REQUESTED;
4836         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4837         SendToICS(buf);
4838     }
4839 }
4840
4841 void
4842 AnalysisPeriodicEvent(force)
4843      int force;
4844 {
4845     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4846          && !force) || !appData.periodicUpdates)
4847       return;
4848
4849     /* Send . command to Crafty to collect stats */
4850     SendToProgram(".\n", &first);
4851
4852     /* Don't send another until we get a response (this makes
4853        us stop sending to old Crafty's which don't understand
4854        the "." command (sending illegal cmds resets node count & time,
4855        which looks bad)) */
4856     programStats.ok_to_send = 0;
4857 }
4858
4859 void ics_update_width(new_width)
4860         int new_width;
4861 {
4862         ics_printf("set width %d\n", new_width);
4863 }
4864
4865 void
4866 SendMoveToProgram(moveNum, cps)
4867      int moveNum;
4868      ChessProgramState *cps;
4869 {
4870     char buf[MSG_SIZ];
4871
4872     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4873         // null move in variant where engine does not understand it (for analysis purposes)
4874         SendBoard(cps, moveNum + 1); // send position after move in stead.
4875         return;
4876     }
4877     if (cps->useUsermove) {
4878       SendToProgram("usermove ", cps);
4879     }
4880     if (cps->useSAN) {
4881       char *space;
4882       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4883         int len = space - parseList[moveNum];
4884         memcpy(buf, parseList[moveNum], len);
4885         buf[len++] = '\n';
4886         buf[len] = NULLCHAR;
4887       } else {
4888         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4889       }
4890       SendToProgram(buf, cps);
4891     } else {
4892       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4893         AlphaRank(moveList[moveNum], 4);
4894         SendToProgram(moveList[moveNum], cps);
4895         AlphaRank(moveList[moveNum], 4); // and back
4896       } else
4897       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4898        * the engine. It would be nice to have a better way to identify castle
4899        * moves here. */
4900       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4901                                                                          && cps->useOOCastle) {
4902         int fromX = moveList[moveNum][0] - AAA;
4903         int fromY = moveList[moveNum][1] - ONE;
4904         int toX = moveList[moveNum][2] - AAA;
4905         int toY = moveList[moveNum][3] - ONE;
4906         if((boards[moveNum][fromY][fromX] == WhiteKing
4907             && boards[moveNum][toY][toX] == WhiteRook)
4908            || (boards[moveNum][fromY][fromX] == BlackKing
4909                && boards[moveNum][toY][toX] == BlackRook)) {
4910           if(toX > fromX) SendToProgram("O-O\n", cps);
4911           else SendToProgram("O-O-O\n", cps);
4912         }
4913         else SendToProgram(moveList[moveNum], cps);
4914       } else
4915       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4916         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4917           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4918           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4919                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4920         } else
4921           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4922                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4923         SendToProgram(buf, cps);
4924       }
4925       else SendToProgram(moveList[moveNum], cps);
4926       /* End of additions by Tord */
4927     }
4928
4929     /* [HGM] setting up the opening has brought engine in force mode! */
4930     /*       Send 'go' if we are in a mode where machine should play. */
4931     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4932         (gameMode == TwoMachinesPlay   ||
4933 #if ZIPPY
4934          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4935 #endif
4936          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4937         SendToProgram("go\n", cps);
4938   if (appData.debugMode) {
4939     fprintf(debugFP, "(extra)\n");
4940   }
4941     }
4942     setboardSpoiledMachineBlack = 0;
4943 }
4944
4945 void
4946 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4947      ChessMove moveType;
4948      int fromX, fromY, toX, toY;
4949      char promoChar;
4950 {
4951     char user_move[MSG_SIZ];
4952
4953     switch (moveType) {
4954       default:
4955         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4956                 (int)moveType, fromX, fromY, toX, toY);
4957         DisplayError(user_move + strlen("say "), 0);
4958         break;
4959       case WhiteKingSideCastle:
4960       case BlackKingSideCastle:
4961       case WhiteQueenSideCastleWild:
4962       case BlackQueenSideCastleWild:
4963       /* PUSH Fabien */
4964       case WhiteHSideCastleFR:
4965       case BlackHSideCastleFR:
4966       /* POP Fabien */
4967         snprintf(user_move, MSG_SIZ, "o-o\n");
4968         break;
4969       case WhiteQueenSideCastle:
4970       case BlackQueenSideCastle:
4971       case WhiteKingSideCastleWild:
4972       case BlackKingSideCastleWild:
4973       /* PUSH Fabien */
4974       case WhiteASideCastleFR:
4975       case BlackASideCastleFR:
4976       /* POP Fabien */
4977         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4978         break;
4979       case WhiteNonPromotion:
4980       case BlackNonPromotion:
4981         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4982         break;
4983       case WhitePromotion:
4984       case BlackPromotion:
4985         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4986           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4987                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4988                 PieceToChar(WhiteFerz));
4989         else if(gameInfo.variant == VariantGreat)
4990           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4991                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4992                 PieceToChar(WhiteMan));
4993         else
4994           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4995                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4996                 promoChar);
4997         break;
4998       case WhiteDrop:
4999       case BlackDrop:
5000       drop:
5001         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5002                  ToUpper(PieceToChar((ChessSquare) fromX)),
5003                  AAA + toX, ONE + toY);
5004         break;
5005       case IllegalMove:  /* could be a variant we don't quite understand */
5006         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5007       case NormalMove:
5008       case WhiteCapturesEnPassant:
5009       case BlackCapturesEnPassant:
5010         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5011                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5012         break;
5013     }
5014     SendToICS(user_move);
5015     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5016         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5017 }
5018
5019 void
5020 UploadGameEvent()
5021 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5022     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5023     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5024     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5025         DisplayError("You cannot do this while you are playing or observing", 0);
5026         return;
5027     }
5028     if(gameMode != IcsExamining) { // is this ever not the case?
5029         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5030
5031         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5032           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5033         } else { // on FICS we must first go to general examine mode
5034           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5035         }
5036         if(gameInfo.variant != VariantNormal) {
5037             // try figure out wild number, as xboard names are not always valid on ICS
5038             for(i=1; i<=36; i++) {
5039               snprintf(buf, MSG_SIZ, "wild/%d", i);
5040                 if(StringToVariant(buf) == gameInfo.variant) break;
5041             }
5042             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5043             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5044             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5045         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5046         SendToICS(ics_prefix);
5047         SendToICS(buf);
5048         if(startedFromSetupPosition || backwardMostMove != 0) {
5049           fen = PositionToFEN(backwardMostMove, NULL);
5050           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5051             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5052             SendToICS(buf);
5053           } else { // FICS: everything has to set by separate bsetup commands
5054             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5055             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5056             SendToICS(buf);
5057             if(!WhiteOnMove(backwardMostMove)) {
5058                 SendToICS("bsetup tomove black\n");
5059             }
5060             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5061             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5062             SendToICS(buf);
5063             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5064             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5065             SendToICS(buf);
5066             i = boards[backwardMostMove][EP_STATUS];
5067             if(i >= 0) { // set e.p.
5068               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5069                 SendToICS(buf);
5070             }
5071             bsetup++;
5072           }
5073         }
5074       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5075             SendToICS("bsetup done\n"); // switch to normal examining.
5076     }
5077     for(i = backwardMostMove; i<last; i++) {
5078         char buf[20];
5079         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5080         SendToICS(buf);
5081     }
5082     SendToICS(ics_prefix);
5083     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5084 }
5085
5086 void
5087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5088      int rf, ff, rt, ft;
5089      char promoChar;
5090      char move[7];
5091 {
5092     if (rf == DROP_RANK) {
5093       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5094       sprintf(move, "%c@%c%c\n",
5095                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5096     } else {
5097         if (promoChar == 'x' || promoChar == NULLCHAR) {
5098           sprintf(move, "%c%c%c%c\n",
5099                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5100         } else {
5101             sprintf(move, "%c%c%c%c%c\n",
5102                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5103         }
5104     }
5105 }
5106
5107 void
5108 ProcessICSInitScript(f)
5109      FILE *f;
5110 {
5111     char buf[MSG_SIZ];
5112
5113     while (fgets(buf, MSG_SIZ, f)) {
5114         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5115     }
5116
5117     fclose(f);
5118 }
5119
5120
5121 static int lastX, lastY, selectFlag, dragging;
5122
5123 void
5124 Sweep(int step)
5125 {
5126     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5127     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5128     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5129     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5130     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5131     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5132     do {
5133         promoSweep -= step;
5134         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5135         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5136         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5137         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5138         if(!step) step = 1;
5139     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5140             appData.testLegality && (promoSweep == king ||
5141             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5142     ChangeDragPiece(promoSweep);
5143 }
5144
5145 int PromoScroll(int x, int y)
5146 {
5147   int step = 0;
5148
5149   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5150   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5151   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5152   if(!step) return FALSE;
5153   lastX = x; lastY = y;
5154   if((promoSweep < BlackPawn) == flipView) step = -step;
5155   if(step > 0) selectFlag = 1;
5156   if(!selectFlag) Sweep(step);
5157   return FALSE;
5158 }
5159
5160 void
5161 NextPiece(int step)
5162 {
5163     ChessSquare piece = boards[currentMove][toY][toX];
5164     do {
5165         pieceSweep -= step;
5166         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5167         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5168         if(!step) step = -1;
5169     } while(PieceToChar(pieceSweep) == '.');
5170     boards[currentMove][toY][toX] = pieceSweep;
5171     DrawPosition(FALSE, boards[currentMove]);
5172     boards[currentMove][toY][toX] = piece;
5173 }
5174 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5175 void
5176 AlphaRank(char *move, int n)
5177 {
5178 //    char *p = move, c; int x, y;
5179
5180     if (appData.debugMode) {
5181         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5182     }
5183
5184     if(move[1]=='*' &&
5185        move[2]>='0' && move[2]<='9' &&
5186        move[3]>='a' && move[3]<='x'    ) {
5187         move[1] = '@';
5188         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5189         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5190     } else
5191     if(move[0]>='0' && move[0]<='9' &&
5192        move[1]>='a' && move[1]<='x' &&
5193        move[2]>='0' && move[2]<='9' &&
5194        move[3]>='a' && move[3]<='x'    ) {
5195         /* input move, Shogi -> normal */
5196         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5197         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5198         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5199         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5200     } else
5201     if(move[1]=='@' &&
5202        move[3]>='0' && move[3]<='9' &&
5203        move[2]>='a' && move[2]<='x'    ) {
5204         move[1] = '*';
5205         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5206         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5207     } else
5208     if(
5209        move[0]>='a' && move[0]<='x' &&
5210        move[3]>='0' && move[3]<='9' &&
5211        move[2]>='a' && move[2]<='x'    ) {
5212          /* output move, normal -> Shogi */
5213         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5214         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5215         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5216         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5217         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5218     }
5219     if (appData.debugMode) {
5220         fprintf(debugFP, "   out = '%s'\n", move);
5221     }
5222 }
5223
5224 char yy_textstr[8000];
5225
5226 /* Parser for moves from gnuchess, ICS, or user typein box */
5227 Boolean
5228 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5229      char *move;
5230      int moveNum;
5231      ChessMove *moveType;
5232      int *fromX, *fromY, *toX, *toY;
5233      char *promoChar;
5234 {
5235     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5236
5237     switch (*moveType) {
5238       case WhitePromotion:
5239       case BlackPromotion:
5240       case WhiteNonPromotion:
5241       case BlackNonPromotion:
5242       case NormalMove:
5243       case WhiteCapturesEnPassant:
5244       case BlackCapturesEnPassant:
5245       case WhiteKingSideCastle:
5246       case WhiteQueenSideCastle:
5247       case BlackKingSideCastle:
5248       case BlackQueenSideCastle:
5249       case WhiteKingSideCastleWild:
5250       case WhiteQueenSideCastleWild:
5251       case BlackKingSideCastleWild:
5252       case BlackQueenSideCastleWild:
5253       /* Code added by Tord: */
5254       case WhiteHSideCastleFR:
5255       case WhiteASideCastleFR:
5256       case BlackHSideCastleFR:
5257       case BlackASideCastleFR:
5258       /* End of code added by Tord */
5259       case IllegalMove:         /* bug or odd chess variant */
5260         *fromX = currentMoveString[0] - AAA;
5261         *fromY = currentMoveString[1] - ONE;
5262         *toX = currentMoveString[2] - AAA;
5263         *toY = currentMoveString[3] - ONE;
5264         *promoChar = currentMoveString[4];
5265         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5266             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5267     if (appData.debugMode) {
5268         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5269     }
5270             *fromX = *fromY = *toX = *toY = 0;
5271             return FALSE;
5272         }
5273         if (appData.testLegality) {
5274           return (*moveType != IllegalMove);
5275         } else {
5276           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5277                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5278         }
5279
5280       case WhiteDrop:
5281       case BlackDrop:
5282         *fromX = *moveType == WhiteDrop ?
5283           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5284           (int) CharToPiece(ToLower(currentMoveString[0]));
5285         *fromY = DROP_RANK;
5286         *toX = currentMoveString[2] - AAA;
5287         *toY = currentMoveString[3] - ONE;
5288         *promoChar = NULLCHAR;
5289         return TRUE;
5290
5291       case AmbiguousMove:
5292       case ImpossibleMove:
5293       case EndOfFile:
5294       case ElapsedTime:
5295       case Comment:
5296       case PGNTag:
5297       case NAG:
5298       case WhiteWins:
5299       case BlackWins:
5300       case GameIsDrawn:
5301       default:
5302     if (appData.debugMode) {
5303         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5304     }
5305         /* bug? */
5306         *fromX = *fromY = *toX = *toY = 0;
5307         *promoChar = NULLCHAR;
5308         return FALSE;
5309     }
5310 }
5311
5312 Boolean pushed = FALSE;
5313 char *lastParseAttempt;
5314
5315 void
5316 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5317 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5318   int fromX, fromY, toX, toY; char promoChar;
5319   ChessMove moveType;
5320   Boolean valid;
5321   int nr = 0;
5322
5323   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5324     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5325     pushed = TRUE;
5326   }
5327   endPV = forwardMostMove;
5328   do {
5329     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5330     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5331     lastParseAttempt = pv;
5332     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5333 if(appData.debugMode){
5334 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5335 }
5336     if(!valid && nr == 0 &&
5337        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5338         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5339         // Hande case where played move is different from leading PV move
5340         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5341         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5342         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5343         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5344           endPV += 2; // if position different, keep this
5345           moveList[endPV-1][0] = fromX + AAA;
5346           moveList[endPV-1][1] = fromY + ONE;
5347           moveList[endPV-1][2] = toX + AAA;
5348           moveList[endPV-1][3] = toY + ONE;
5349           parseList[endPV-1][0] = NULLCHAR;
5350           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5351         }
5352       }
5353     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5354     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5355     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5356     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5357         valid++; // allow comments in PV
5358         continue;
5359     }
5360     nr++;
5361     if(endPV+1 > framePtr) break; // no space, truncate
5362     if(!valid) break;
5363     endPV++;
5364     CopyBoard(boards[endPV], boards[endPV-1]);
5365     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5366     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5367     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5368     CoordsToAlgebraic(boards[endPV - 1],
5369                              PosFlags(endPV - 1),
5370                              fromY, fromX, toY, toX, promoChar,
5371                              parseList[endPV - 1]);
5372   } while(valid);
5373   if(atEnd == 2) return; // used hidden, for PV conversion
5374   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5375   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5376   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5377                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5378   DrawPosition(TRUE, boards[currentMove]);
5379 }
5380
5381 int
5382 MultiPV(ChessProgramState *cps)
5383 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5384         int i;
5385         for(i=0; i<cps->nrOptions; i++)
5386             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5387                 return i;
5388         return -1;
5389 }
5390
5391 Boolean
5392 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5393 {
5394         int startPV, multi, lineStart, origIndex = index;
5395         char *p, buf2[MSG_SIZ];
5396
5397         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5398         lastX = x; lastY = y;
5399         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5400         lineStart = startPV = index;
5401         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5402         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5403         index = startPV;
5404         do{ while(buf[index] && buf[index] != '\n') index++;
5405         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5406         buf[index] = 0;
5407         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5408                 int n = first.option[multi].value;
5409                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5410                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5411                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5412                 first.option[multi].value = n;
5413                 *start = *end = 0;
5414                 return FALSE;
5415         }
5416         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5417         *start = startPV; *end = index-1;
5418         return TRUE;
5419 }
5420
5421 char *
5422 PvToSAN(char *pv)
5423 {
5424         static char buf[10*MSG_SIZ];
5425         int i, k=0, savedEnd=endPV;
5426         *buf = NULLCHAR;
5427         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5428         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5429         for(i = forwardMostMove; i<endPV; i++){
5430             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5431             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5432             k += strlen(buf+k);
5433         }
5434         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5435         if(forwardMostMove < savedEnd) PopInner(0);
5436         endPV = savedEnd;
5437         return buf;
5438 }
5439
5440 Boolean
5441 LoadPV(int x, int y)
5442 { // called on right mouse click to load PV
5443   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5444   lastX = x; lastY = y;
5445   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5446   return TRUE;
5447 }
5448
5449 void
5450 UnLoadPV()
5451 {
5452   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5453   if(endPV < 0) return;
5454   endPV = -1;
5455   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5456         Boolean saveAnimate = appData.animate;
5457         if(pushed) {
5458             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5459                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5460             } else storedGames--; // abandon shelved tail of original game
5461         }
5462         pushed = FALSE;
5463         forwardMostMove = currentMove;
5464         currentMove = oldFMM;
5465         appData.animate = FALSE;
5466         ToNrEvent(forwardMostMove);
5467         appData.animate = saveAnimate;
5468   }
5469   currentMove = forwardMostMove;
5470   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5471   ClearPremoveHighlights();
5472   DrawPosition(TRUE, boards[currentMove]);
5473 }
5474
5475 void
5476 MovePV(int x, int y, int h)
5477 { // step through PV based on mouse coordinates (called on mouse move)
5478   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5479
5480   // we must somehow check if right button is still down (might be released off board!)
5481   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5482   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5483   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5484   if(!step) return;
5485   lastX = x; lastY = y;
5486
5487   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5488   if(endPV < 0) return;
5489   if(y < margin) step = 1; else
5490   if(y > h - margin) step = -1;
5491   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5492   currentMove += step;
5493   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5494   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5495                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5496   DrawPosition(FALSE, boards[currentMove]);
5497 }
5498
5499
5500 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5501 // All positions will have equal probability, but the current method will not provide a unique
5502 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5503 #define DARK 1
5504 #define LITE 2
5505 #define ANY 3
5506
5507 int squaresLeft[4];
5508 int piecesLeft[(int)BlackPawn];
5509 int seed, nrOfShuffles;
5510
5511 void GetPositionNumber()
5512 {       // sets global variable seed
5513         int i;
5514
5515         seed = appData.defaultFrcPosition;
5516         if(seed < 0) { // randomize based on time for negative FRC position numbers
5517                 for(i=0; i<50; i++) seed += random();
5518                 seed = random() ^ random() >> 8 ^ random() << 8;
5519                 if(seed<0) seed = -seed;
5520         }
5521 }
5522
5523 int put(Board board, int pieceType, int rank, int n, int shade)
5524 // put the piece on the (n-1)-th empty squares of the given shade
5525 {
5526         int i;
5527
5528         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5529                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5530                         board[rank][i] = (ChessSquare) pieceType;
5531                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5532                         squaresLeft[ANY]--;
5533                         piecesLeft[pieceType]--;
5534                         return i;
5535                 }
5536         }
5537         return -1;
5538 }
5539
5540
5541 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5542 // calculate where the next piece goes, (any empty square), and put it there
5543 {
5544         int i;
5545
5546         i = seed % squaresLeft[shade];
5547         nrOfShuffles *= squaresLeft[shade];
5548         seed /= squaresLeft[shade];
5549         put(board, pieceType, rank, i, shade);
5550 }
5551
5552 void AddTwoPieces(Board board, int pieceType, int rank)
5553 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5554 {
5555         int i, n=squaresLeft[ANY], j=n-1, k;
5556
5557         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5558         i = seed % k;  // pick one
5559         nrOfShuffles *= k;
5560         seed /= k;
5561         while(i >= j) i -= j--;
5562         j = n - 1 - j; i += j;
5563         put(board, pieceType, rank, j, ANY);
5564         put(board, pieceType, rank, i, ANY);
5565 }
5566
5567 void SetUpShuffle(Board board, int number)
5568 {
5569         int i, p, first=1;
5570
5571         GetPositionNumber(); nrOfShuffles = 1;
5572
5573         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5574         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5575         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5576
5577         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5578
5579         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5580             p = (int) board[0][i];
5581             if(p < (int) BlackPawn) piecesLeft[p] ++;
5582             board[0][i] = EmptySquare;
5583         }
5584
5585         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5586             // shuffles restricted to allow normal castling put KRR first
5587             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5588                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5589             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5590                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5591             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5592                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5593             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5594                 put(board, WhiteRook, 0, 0, ANY);
5595             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5596         }
5597
5598         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5599             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5600             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5601                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5602                 while(piecesLeft[p] >= 2) {
5603                     AddOnePiece(board, p, 0, LITE);
5604                     AddOnePiece(board, p, 0, DARK);
5605                 }
5606                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5607             }
5608
5609         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5610             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5611             // but we leave King and Rooks for last, to possibly obey FRC restriction
5612             if(p == (int)WhiteRook) continue;
5613             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5614             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5615         }
5616
5617         // now everything is placed, except perhaps King (Unicorn) and Rooks
5618
5619         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5620             // Last King gets castling rights
5621             while(piecesLeft[(int)WhiteUnicorn]) {
5622                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5623                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5624             }
5625
5626             while(piecesLeft[(int)WhiteKing]) {
5627                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5628                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5629             }
5630
5631
5632         } else {
5633             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5634             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5635         }
5636
5637         // Only Rooks can be left; simply place them all
5638         while(piecesLeft[(int)WhiteRook]) {
5639                 i = put(board, WhiteRook, 0, 0, ANY);
5640                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5641                         if(first) {
5642                                 first=0;
5643                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5644                         }
5645                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5646                 }
5647         }
5648         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5649             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5650         }
5651
5652         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5653 }
5654
5655 int SetCharTable( char *table, const char * map )
5656 /* [HGM] moved here from winboard.c because of its general usefulness */
5657 /*       Basically a safe strcpy that uses the last character as King */
5658 {
5659     int result = FALSE; int NrPieces;
5660
5661     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5662                     && NrPieces >= 12 && !(NrPieces&1)) {
5663         int i; /* [HGM] Accept even length from 12 to 34 */
5664
5665         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5666         for( i=0; i<NrPieces/2-1; i++ ) {
5667             table[i] = map[i];
5668             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5669         }
5670         table[(int) WhiteKing]  = map[NrPieces/2-1];
5671         table[(int) BlackKing]  = map[NrPieces-1];
5672
5673         result = TRUE;
5674     }
5675
5676     return result;
5677 }
5678
5679 void Prelude(Board board)
5680 {       // [HGM] superchess: random selection of exo-pieces
5681         int i, j, k; ChessSquare p;
5682         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5683
5684         GetPositionNumber(); // use FRC position number
5685
5686         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5687             SetCharTable(pieceToChar, appData.pieceToCharTable);
5688             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5689                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5690         }
5691
5692         j = seed%4;                 seed /= 4;
5693         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5694         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5695         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5696         j = seed%3 + (seed%3 >= j); seed /= 3;
5697         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5700         j = seed%3;                 seed /= 3;
5701         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%2 + (seed%2 >= j); seed /= 2;
5705         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5709         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5710         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5711         put(board, exoPieces[0],    0, 0, ANY);
5712         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5713 }
5714
5715 void
5716 InitPosition(redraw)
5717      int redraw;
5718 {
5719     ChessSquare (* pieces)[BOARD_FILES];
5720     int i, j, pawnRow, overrule,
5721     oldx = gameInfo.boardWidth,
5722     oldy = gameInfo.boardHeight,
5723     oldh = gameInfo.holdingsWidth;
5724     static int oldv;
5725
5726     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5727
5728     /* [AS] Initialize pv info list [HGM] and game status */
5729     {
5730         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5731             pvInfoList[i].depth = 0;
5732             boards[i][EP_STATUS] = EP_NONE;
5733             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5734         }
5735
5736         initialRulePlies = 0; /* 50-move counter start */
5737
5738         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5739         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5740     }
5741
5742
5743     /* [HGM] logic here is completely changed. In stead of full positions */
5744     /* the initialized data only consist of the two backranks. The switch */
5745     /* selects which one we will use, which is than copied to the Board   */
5746     /* initialPosition, which for the rest is initialized by Pawns and    */
5747     /* empty squares. This initial position is then copied to boards[0],  */
5748     /* possibly after shuffling, so that it remains available.            */
5749
5750     gameInfo.holdingsWidth = 0; /* default board sizes */
5751     gameInfo.boardWidth    = 8;
5752     gameInfo.boardHeight   = 8;
5753     gameInfo.holdingsSize  = 0;
5754     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5755     for(i=0; i<BOARD_FILES-2; i++)
5756       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5757     initialPosition[EP_STATUS] = EP_NONE;
5758     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5759     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5760          SetCharTable(pieceNickName, appData.pieceNickNames);
5761     else SetCharTable(pieceNickName, "............");
5762     pieces = FIDEArray;
5763
5764     switch (gameInfo.variant) {
5765     case VariantFischeRandom:
5766       shuffleOpenings = TRUE;
5767     default:
5768       break;
5769     case VariantShatranj:
5770       pieces = ShatranjArray;
5771       nrCastlingRights = 0;
5772       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5773       break;
5774     case VariantMakruk:
5775       pieces = makrukArray;
5776       nrCastlingRights = 0;
5777       startedFromSetupPosition = TRUE;
5778       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5779       break;
5780     case VariantTwoKings:
5781       pieces = twoKingsArray;
5782       break;
5783     case VariantGrand:
5784       pieces = GrandArray;
5785       nrCastlingRights = 0;
5786       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5787       gameInfo.boardWidth = 10;
5788       gameInfo.boardHeight = 10;
5789       gameInfo.holdingsSize = 7;
5790       break;
5791     case VariantCapaRandom:
5792       shuffleOpenings = TRUE;
5793     case VariantCapablanca:
5794       pieces = CapablancaArray;
5795       gameInfo.boardWidth = 10;
5796       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5797       break;
5798     case VariantGothic:
5799       pieces = GothicArray;
5800       gameInfo.boardWidth = 10;
5801       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5802       break;
5803     case VariantSChess:
5804       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5805       gameInfo.holdingsSize = 7;
5806       break;
5807     case VariantJanus:
5808       pieces = JanusArray;
5809       gameInfo.boardWidth = 10;
5810       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5811       nrCastlingRights = 6;
5812         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5813         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5814         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5815         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5816         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5817         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5818       break;
5819     case VariantFalcon:
5820       pieces = FalconArray;
5821       gameInfo.boardWidth = 10;
5822       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5823       break;
5824     case VariantXiangqi:
5825       pieces = XiangqiArray;
5826       gameInfo.boardWidth  = 9;
5827       gameInfo.boardHeight = 10;
5828       nrCastlingRights = 0;
5829       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5830       break;
5831     case VariantShogi:
5832       pieces = ShogiArray;
5833       gameInfo.boardWidth  = 9;
5834       gameInfo.boardHeight = 9;
5835       gameInfo.holdingsSize = 7;
5836       nrCastlingRights = 0;
5837       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5838       break;
5839     case VariantCourier:
5840       pieces = CourierArray;
5841       gameInfo.boardWidth  = 12;
5842       nrCastlingRights = 0;
5843       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5844       break;
5845     case VariantKnightmate:
5846       pieces = KnightmateArray;
5847       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5848       break;
5849     case VariantSpartan:
5850       pieces = SpartanArray;
5851       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5852       break;
5853     case VariantFairy:
5854       pieces = fairyArray;
5855       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5856       break;
5857     case VariantGreat:
5858       pieces = GreatArray;
5859       gameInfo.boardWidth = 10;
5860       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5861       gameInfo.holdingsSize = 8;
5862       break;
5863     case VariantSuper:
5864       pieces = FIDEArray;
5865       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5866       gameInfo.holdingsSize = 8;
5867       startedFromSetupPosition = TRUE;
5868       break;
5869     case VariantCrazyhouse:
5870     case VariantBughouse:
5871       pieces = FIDEArray;
5872       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5873       gameInfo.holdingsSize = 5;
5874       break;
5875     case VariantWildCastle:
5876       pieces = FIDEArray;
5877       /* !!?shuffle with kings guaranteed to be on d or e file */
5878       shuffleOpenings = 1;
5879       break;
5880     case VariantNoCastle:
5881       pieces = FIDEArray;
5882       nrCastlingRights = 0;
5883       /* !!?unconstrained back-rank shuffle */
5884       shuffleOpenings = 1;
5885       break;
5886     }
5887
5888     overrule = 0;
5889     if(appData.NrFiles >= 0) {
5890         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5891         gameInfo.boardWidth = appData.NrFiles;
5892     }
5893     if(appData.NrRanks >= 0) {
5894         gameInfo.boardHeight = appData.NrRanks;
5895     }
5896     if(appData.holdingsSize >= 0) {
5897         i = appData.holdingsSize;
5898         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5899         gameInfo.holdingsSize = i;
5900     }
5901     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5902     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5903         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5904
5905     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5906     if(pawnRow < 1) pawnRow = 1;
5907     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5908
5909     /* User pieceToChar list overrules defaults */
5910     if(appData.pieceToCharTable != NULL)
5911         SetCharTable(pieceToChar, appData.pieceToCharTable);
5912
5913     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5914
5915         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5916             s = (ChessSquare) 0; /* account holding counts in guard band */
5917         for( i=0; i<BOARD_HEIGHT; i++ )
5918             initialPosition[i][j] = s;
5919
5920         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5921         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5922         initialPosition[pawnRow][j] = WhitePawn;
5923         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5924         if(gameInfo.variant == VariantXiangqi) {
5925             if(j&1) {
5926                 initialPosition[pawnRow][j] =
5927                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5928                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5929                    initialPosition[2][j] = WhiteCannon;
5930                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5931                 }
5932             }
5933         }
5934         if(gameInfo.variant == VariantGrand) {
5935             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5936                initialPosition[0][j] = WhiteRook;
5937                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5938             }
5939         }
5940         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5941     }
5942     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5943
5944             j=BOARD_LEFT+1;
5945             initialPosition[1][j] = WhiteBishop;
5946             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5947             j=BOARD_RGHT-2;
5948             initialPosition[1][j] = WhiteRook;
5949             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5950     }
5951
5952     if( nrCastlingRights == -1) {
5953         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5954         /*       This sets default castling rights from none to normal corners   */
5955         /* Variants with other castling rights must set them themselves above    */
5956         nrCastlingRights = 6;
5957
5958         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5959         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5960         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5961         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5962         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5963         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5964      }
5965
5966      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5967      if(gameInfo.variant == VariantGreat) { // promotion commoners
5968         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5969         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5970         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5971         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5972      }
5973      if( gameInfo.variant == VariantSChess ) {
5974       initialPosition[1][0] = BlackMarshall;
5975       initialPosition[2][0] = BlackAngel;
5976       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5977       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5978       initialPosition[1][1] = initialPosition[2][1] = 
5979       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5980      }
5981   if (appData.debugMode) {
5982     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5983   }
5984     if(shuffleOpenings) {
5985         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5986         startedFromSetupPosition = TRUE;
5987     }
5988     if(startedFromPositionFile) {
5989       /* [HGM] loadPos: use PositionFile for every new game */
5990       CopyBoard(initialPosition, filePosition);
5991       for(i=0; i<nrCastlingRights; i++)
5992           initialRights[i] = filePosition[CASTLING][i];
5993       startedFromSetupPosition = TRUE;
5994     }
5995
5996     CopyBoard(boards[0], initialPosition);
5997
5998     if(oldx != gameInfo.boardWidth ||
5999        oldy != gameInfo.boardHeight ||
6000        oldv != gameInfo.variant ||
6001        oldh != gameInfo.holdingsWidth
6002                                          )
6003             InitDrawingSizes(-2 ,0);
6004
6005     oldv = gameInfo.variant;
6006     if (redraw)
6007       DrawPosition(TRUE, boards[currentMove]);
6008 }
6009
6010 void
6011 SendBoard(cps, moveNum)
6012      ChessProgramState *cps;
6013      int moveNum;
6014 {
6015     char message[MSG_SIZ];
6016
6017     if (cps->useSetboard) {
6018       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6019       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6020       SendToProgram(message, cps);
6021       free(fen);
6022
6023     } else {
6024       ChessSquare *bp;
6025       int i, j;
6026       /* Kludge to set black to move, avoiding the troublesome and now
6027        * deprecated "black" command.
6028        */
6029       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6030         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6031
6032       SendToProgram("edit\n", cps);
6033       SendToProgram("#\n", cps);
6034       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6035         bp = &boards[moveNum][i][BOARD_LEFT];
6036         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6037           if ((int) *bp < (int) BlackPawn) {
6038             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6039                     AAA + j, ONE + i);
6040             if(message[0] == '+' || message[0] == '~') {
6041               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6042                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6043                         AAA + j, ONE + i);
6044             }
6045             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6046                 message[1] = BOARD_RGHT   - 1 - j + '1';
6047                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6048             }
6049             SendToProgram(message, cps);
6050           }
6051         }
6052       }
6053
6054       SendToProgram("c\n", cps);
6055       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6056         bp = &boards[moveNum][i][BOARD_LEFT];
6057         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6058           if (((int) *bp != (int) EmptySquare)
6059               && ((int) *bp >= (int) BlackPawn)) {
6060             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6061                     AAA + j, ONE + i);
6062             if(message[0] == '+' || message[0] == '~') {
6063               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6064                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6065                         AAA + j, ONE + i);
6066             }
6067             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6068                 message[1] = BOARD_RGHT   - 1 - j + '1';
6069                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6070             }
6071             SendToProgram(message, cps);
6072           }
6073         }
6074       }
6075
6076       SendToProgram(".\n", cps);
6077     }
6078     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6079 }
6080
6081 ChessSquare
6082 DefaultPromoChoice(int white)
6083 {
6084     ChessSquare result;
6085     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6086         result = WhiteFerz; // no choice
6087     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6088         result= WhiteKing; // in Suicide Q is the last thing we want
6089     else if(gameInfo.variant == VariantSpartan)
6090         result = white ? WhiteQueen : WhiteAngel;
6091     else result = WhiteQueen;
6092     if(!white) result = WHITE_TO_BLACK result;
6093     return result;
6094 }
6095
6096 static int autoQueen; // [HGM] oneclick
6097
6098 int
6099 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6100 {
6101     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6102     /* [HGM] add Shogi promotions */
6103     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6104     ChessSquare piece;
6105     ChessMove moveType;
6106     Boolean premove;
6107
6108     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6109     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6110
6111     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6112       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6113         return FALSE;
6114
6115     piece = boards[currentMove][fromY][fromX];
6116     if(gameInfo.variant == VariantShogi) {
6117         promotionZoneSize = BOARD_HEIGHT/3;
6118         highestPromotingPiece = (int)WhiteFerz;
6119     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6120         promotionZoneSize = 3;
6121     }
6122
6123     // Treat Lance as Pawn when it is not representing Amazon
6124     if(gameInfo.variant != VariantSuper) {
6125         if(piece == WhiteLance) piece = WhitePawn; else
6126         if(piece == BlackLance) piece = BlackPawn;
6127     }
6128
6129     // next weed out all moves that do not touch the promotion zone at all
6130     if((int)piece >= BlackPawn) {
6131         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6132              return FALSE;
6133         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6134     } else {
6135         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6136            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6137     }
6138
6139     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6140
6141     // weed out mandatory Shogi promotions
6142     if(gameInfo.variant == VariantShogi) {
6143         if(piece >= BlackPawn) {
6144             if(toY == 0 && piece == BlackPawn ||
6145                toY == 0 && piece == BlackQueen ||
6146                toY <= 1 && piece == BlackKnight) {
6147                 *promoChoice = '+';
6148                 return FALSE;
6149             }
6150         } else {
6151             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6152                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6153                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6154                 *promoChoice = '+';
6155                 return FALSE;
6156             }
6157         }
6158     }
6159
6160     // weed out obviously illegal Pawn moves
6161     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6162         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6163         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6164         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6165         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6166         // note we are not allowed to test for valid (non-)capture, due to premove
6167     }
6168
6169     // we either have a choice what to promote to, or (in Shogi) whether to promote
6170     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6171         *promoChoice = PieceToChar(BlackFerz);  // no choice
6172         return FALSE;
6173     }
6174     // no sense asking what we must promote to if it is going to explode...
6175     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6176         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6177         return FALSE;
6178     }
6179     // give caller the default choice even if we will not make it
6180     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6181     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6182     if(        sweepSelect && gameInfo.variant != VariantGreat
6183                            && gameInfo.variant != VariantGrand
6184                            && gameInfo.variant != VariantSuper) return FALSE;
6185     if(autoQueen) return FALSE; // predetermined
6186
6187     // suppress promotion popup on illegal moves that are not premoves
6188     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6189               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6190     if(appData.testLegality && !premove) {
6191         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6192                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6193         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6194             return FALSE;
6195     }
6196
6197     return TRUE;
6198 }
6199
6200 int
6201 InPalace(row, column)
6202      int row, column;
6203 {   /* [HGM] for Xiangqi */
6204     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6205          column < (BOARD_WIDTH + 4)/2 &&
6206          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6207     return FALSE;
6208 }
6209
6210 int
6211 PieceForSquare (x, y)
6212      int x;
6213      int y;
6214 {
6215   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6216      return -1;
6217   else
6218      return boards[currentMove][y][x];
6219 }
6220
6221 int
6222 OKToStartUserMove(x, y)
6223      int x, y;
6224 {
6225     ChessSquare from_piece;
6226     int white_piece;
6227
6228     if (matchMode) return FALSE;
6229     if (gameMode == EditPosition) return TRUE;
6230
6231     if (x >= 0 && y >= 0)
6232       from_piece = boards[currentMove][y][x];
6233     else
6234       from_piece = EmptySquare;
6235
6236     if (from_piece == EmptySquare) return FALSE;
6237
6238     white_piece = (int)from_piece >= (int)WhitePawn &&
6239       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6240
6241     switch (gameMode) {
6242       case AnalyzeFile:
6243       case TwoMachinesPlay:
6244       case EndOfGame:
6245         return FALSE;
6246
6247       case IcsObserving:
6248       case IcsIdle:
6249         return FALSE;
6250
6251       case MachinePlaysWhite:
6252       case IcsPlayingBlack:
6253         if (appData.zippyPlay) return FALSE;
6254         if (white_piece) {
6255             DisplayMoveError(_("You are playing Black"));
6256             return FALSE;
6257         }
6258         break;
6259
6260       case MachinePlaysBlack:
6261       case IcsPlayingWhite:
6262         if (appData.zippyPlay) return FALSE;
6263         if (!white_piece) {
6264             DisplayMoveError(_("You are playing White"));
6265             return FALSE;
6266         }
6267         break;
6268
6269       case PlayFromGameFile:
6270             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6271       case EditGame:
6272         if (!white_piece && WhiteOnMove(currentMove)) {
6273             DisplayMoveError(_("It is White's turn"));
6274             return FALSE;
6275         }
6276         if (white_piece && !WhiteOnMove(currentMove)) {
6277             DisplayMoveError(_("It is Black's turn"));
6278             return FALSE;
6279         }
6280         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6281             /* Editing correspondence game history */
6282             /* Could disallow this or prompt for confirmation */
6283             cmailOldMove = -1;
6284         }
6285         break;
6286
6287       case BeginningOfGame:
6288         if (appData.icsActive) return FALSE;
6289         if (!appData.noChessProgram) {
6290             if (!white_piece) {
6291                 DisplayMoveError(_("You are playing White"));
6292                 return FALSE;
6293             }
6294         }
6295         break;
6296
6297       case Training:
6298         if (!white_piece && WhiteOnMove(currentMove)) {
6299             DisplayMoveError(_("It is White's turn"));
6300             return FALSE;
6301         }
6302         if (white_piece && !WhiteOnMove(currentMove)) {
6303             DisplayMoveError(_("It is Black's turn"));
6304             return FALSE;
6305         }
6306         break;
6307
6308       default:
6309       case IcsExamining:
6310         break;
6311     }
6312     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6313         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6314         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6315         && gameMode != AnalyzeFile && gameMode != Training) {
6316         DisplayMoveError(_("Displayed position is not current"));
6317         return FALSE;
6318     }
6319     return TRUE;
6320 }
6321
6322 Boolean
6323 OnlyMove(int *x, int *y, Boolean captures) {
6324     DisambiguateClosure cl;
6325     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6326     switch(gameMode) {
6327       case MachinePlaysBlack:
6328       case IcsPlayingWhite:
6329       case BeginningOfGame:
6330         if(!WhiteOnMove(currentMove)) return FALSE;
6331         break;
6332       case MachinePlaysWhite:
6333       case IcsPlayingBlack:
6334         if(WhiteOnMove(currentMove)) return FALSE;
6335         break;
6336       case EditGame:
6337         break;
6338       default:
6339         return FALSE;
6340     }
6341     cl.pieceIn = EmptySquare;
6342     cl.rfIn = *y;
6343     cl.ffIn = *x;
6344     cl.rtIn = -1;
6345     cl.ftIn = -1;
6346     cl.promoCharIn = NULLCHAR;
6347     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6348     if( cl.kind == NormalMove ||
6349         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6350         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6351         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6352       fromX = cl.ff;
6353       fromY = cl.rf;
6354       *x = cl.ft;
6355       *y = cl.rt;
6356       return TRUE;
6357     }
6358     if(cl.kind != ImpossibleMove) return FALSE;
6359     cl.pieceIn = EmptySquare;
6360     cl.rfIn = -1;
6361     cl.ffIn = -1;
6362     cl.rtIn = *y;
6363     cl.ftIn = *x;
6364     cl.promoCharIn = NULLCHAR;
6365     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6366     if( cl.kind == NormalMove ||
6367         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6368         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6369         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6370       fromX = cl.ff;
6371       fromY = cl.rf;
6372       *x = cl.ft;
6373       *y = cl.rt;
6374       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6375       return TRUE;
6376     }
6377     return FALSE;
6378 }
6379
6380 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6381 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6382 int lastLoadGameUseList = FALSE;
6383 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6384 ChessMove lastLoadGameStart = EndOfFile;
6385
6386 void
6387 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6388      int fromX, fromY, toX, toY;
6389      int promoChar;
6390 {
6391     ChessMove moveType;
6392     ChessSquare pdown, pup;
6393
6394     /* Check if the user is playing in turn.  This is complicated because we
6395        let the user "pick up" a piece before it is his turn.  So the piece he
6396        tried to pick up may have been captured by the time he puts it down!
6397        Therefore we use the color the user is supposed to be playing in this
6398        test, not the color of the piece that is currently on the starting
6399        square---except in EditGame mode, where the user is playing both
6400        sides; fortunately there the capture race can't happen.  (It can
6401        now happen in IcsExamining mode, but that's just too bad.  The user
6402        will get a somewhat confusing message in that case.)
6403        */
6404
6405     switch (gameMode) {
6406       case AnalyzeFile:
6407       case TwoMachinesPlay:
6408       case EndOfGame:
6409       case IcsObserving:
6410       case IcsIdle:
6411         /* We switched into a game mode where moves are not accepted,
6412            perhaps while the mouse button was down. */
6413         return;
6414
6415       case MachinePlaysWhite:
6416         /* User is moving for Black */
6417         if (WhiteOnMove(currentMove)) {
6418             DisplayMoveError(_("It is White's turn"));
6419             return;
6420         }
6421         break;
6422
6423       case MachinePlaysBlack:
6424         /* User is moving for White */
6425         if (!WhiteOnMove(currentMove)) {
6426             DisplayMoveError(_("It is Black's turn"));
6427             return;
6428         }
6429         break;
6430
6431       case PlayFromGameFile:
6432             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6433       case EditGame:
6434       case IcsExamining:
6435       case BeginningOfGame:
6436       case AnalyzeMode:
6437       case Training:
6438         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6439         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6440             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6441             /* User is moving for Black */
6442             if (WhiteOnMove(currentMove)) {
6443                 DisplayMoveError(_("It is White's turn"));
6444                 return;
6445             }
6446         } else {
6447             /* User is moving for White */
6448             if (!WhiteOnMove(currentMove)) {
6449                 DisplayMoveError(_("It is Black's turn"));
6450                 return;
6451             }
6452         }
6453         break;
6454
6455       case IcsPlayingBlack:
6456         /* User is moving for Black */
6457         if (WhiteOnMove(currentMove)) {
6458             if (!appData.premove) {
6459                 DisplayMoveError(_("It is White's turn"));
6460             } else if (toX >= 0 && toY >= 0) {
6461                 premoveToX = toX;
6462                 premoveToY = toY;
6463                 premoveFromX = fromX;
6464                 premoveFromY = fromY;
6465                 premovePromoChar = promoChar;
6466                 gotPremove = 1;
6467                 if (appData.debugMode)
6468                     fprintf(debugFP, "Got premove: fromX %d,"
6469                             "fromY %d, toX %d, toY %d\n",
6470                             fromX, fromY, toX, toY);
6471             }
6472             return;
6473         }
6474         break;
6475
6476       case IcsPlayingWhite:
6477         /* User is moving for White */
6478         if (!WhiteOnMove(currentMove)) {
6479             if (!appData.premove) {
6480                 DisplayMoveError(_("It is Black's turn"));
6481             } else if (toX >= 0 && toY >= 0) {
6482                 premoveToX = toX;
6483                 premoveToY = toY;
6484                 premoveFromX = fromX;
6485                 premoveFromY = fromY;
6486                 premovePromoChar = promoChar;
6487                 gotPremove = 1;
6488                 if (appData.debugMode)
6489                     fprintf(debugFP, "Got premove: fromX %d,"
6490                             "fromY %d, toX %d, toY %d\n",
6491                             fromX, fromY, toX, toY);
6492             }
6493             return;
6494         }
6495         break;
6496
6497       default:
6498         break;
6499
6500       case EditPosition:
6501         /* EditPosition, empty square, or different color piece;
6502            click-click move is possible */
6503         if (toX == -2 || toY == -2) {
6504             boards[0][fromY][fromX] = EmptySquare;
6505             DrawPosition(FALSE, boards[currentMove]);
6506             return;
6507         } else if (toX >= 0 && toY >= 0) {
6508             boards[0][toY][toX] = boards[0][fromY][fromX];
6509             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6510                 if(boards[0][fromY][0] != EmptySquare) {
6511                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6512                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6513                 }
6514             } else
6515             if(fromX == BOARD_RGHT+1) {
6516                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6517                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6518                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6519                 }
6520             } else
6521             boards[0][fromY][fromX] = EmptySquare;
6522             DrawPosition(FALSE, boards[currentMove]);
6523             return;
6524         }
6525         return;
6526     }
6527
6528     if(toX < 0 || toY < 0) return;
6529     pdown = boards[currentMove][fromY][fromX];
6530     pup = boards[currentMove][toY][toX];
6531
6532     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6533     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6534          if( pup != EmptySquare ) return;
6535          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6536            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6537                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6538            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6539            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6540            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6541            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6542          fromY = DROP_RANK;
6543     }
6544
6545     /* [HGM] always test for legality, to get promotion info */
6546     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6547                                          fromY, fromX, toY, toX, promoChar);
6548
6549     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6550
6551     /* [HGM] but possibly ignore an IllegalMove result */
6552     if (appData.testLegality) {
6553         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6554             DisplayMoveError(_("Illegal move"));
6555             return;
6556         }
6557     }
6558
6559     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6560 }
6561
6562 /* Common tail of UserMoveEvent and DropMenuEvent */
6563 int
6564 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6565      ChessMove moveType;
6566      int fromX, fromY, toX, toY;
6567      /*char*/int promoChar;
6568 {
6569     char *bookHit = 0;
6570
6571     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6572         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6573         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6574         if(WhiteOnMove(currentMove)) {
6575             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6576         } else {
6577             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6578         }
6579     }
6580
6581     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6582        move type in caller when we know the move is a legal promotion */
6583     if(moveType == NormalMove && promoChar)
6584         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6585
6586     /* [HGM] <popupFix> The following if has been moved here from
6587        UserMoveEvent(). Because it seemed to belong here (why not allow
6588        piece drops in training games?), and because it can only be
6589        performed after it is known to what we promote. */
6590     if (gameMode == Training) {
6591       /* compare the move played on the board to the next move in the
6592        * game. If they match, display the move and the opponent's response.
6593        * If they don't match, display an error message.
6594        */
6595       int saveAnimate;
6596       Board testBoard;
6597       CopyBoard(testBoard, boards[currentMove]);
6598       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6599
6600       if (CompareBoards(testBoard, boards[currentMove+1])) {
6601         ForwardInner(currentMove+1);
6602
6603         /* Autoplay the opponent's response.
6604          * if appData.animate was TRUE when Training mode was entered,
6605          * the response will be animated.
6606          */
6607         saveAnimate = appData.animate;
6608         appData.animate = animateTraining;
6609         ForwardInner(currentMove+1);
6610         appData.animate = saveAnimate;
6611
6612         /* check for the end of the game */
6613         if (currentMove >= forwardMostMove) {
6614           gameMode = PlayFromGameFile;
6615           ModeHighlight();
6616           SetTrainingModeOff();
6617           DisplayInformation(_("End of game"));
6618         }
6619       } else {
6620         DisplayError(_("Incorrect move"), 0);
6621       }
6622       return 1;
6623     }
6624
6625   /* Ok, now we know that the move is good, so we can kill
6626      the previous line in Analysis Mode */
6627   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6628                                 && currentMove < forwardMostMove) {
6629     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6630     else forwardMostMove = currentMove;
6631   }
6632
6633   /* If we need the chess program but it's dead, restart it */
6634   ResurrectChessProgram();
6635
6636   /* A user move restarts a paused game*/
6637   if (pausing)
6638     PauseEvent();
6639
6640   thinkOutput[0] = NULLCHAR;
6641
6642   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6643
6644   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6645     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6646     return 1;
6647   }
6648
6649   if (gameMode == BeginningOfGame) {
6650     if (appData.noChessProgram) {
6651       gameMode = EditGame;
6652       SetGameInfo();
6653     } else {
6654       char buf[MSG_SIZ];
6655       gameMode = MachinePlaysBlack;
6656       StartClocks();
6657       SetGameInfo();
6658       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6659       DisplayTitle(buf);
6660       if (first.sendName) {
6661         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6662         SendToProgram(buf, &first);
6663       }
6664       StartClocks();
6665     }
6666     ModeHighlight();
6667   }
6668
6669   /* Relay move to ICS or chess engine */
6670   if (appData.icsActive) {
6671     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6672         gameMode == IcsExamining) {
6673       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6674         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6675         SendToICS("draw ");
6676         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6677       }
6678       // also send plain move, in case ICS does not understand atomic claims
6679       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6680       ics_user_moved = 1;
6681     }
6682   } else {
6683     if (first.sendTime && (gameMode == BeginningOfGame ||
6684                            gameMode == MachinePlaysWhite ||
6685                            gameMode == MachinePlaysBlack)) {
6686       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6687     }
6688     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6689          // [HGM] book: if program might be playing, let it use book
6690         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6691         first.maybeThinking = TRUE;
6692     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6693         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6694         SendBoard(&first, currentMove+1);
6695     } else SendMoveToProgram(forwardMostMove-1, &first);
6696     if (currentMove == cmailOldMove + 1) {
6697       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6698     }
6699   }
6700
6701   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6702
6703   switch (gameMode) {
6704   case EditGame:
6705     if(appData.testLegality)
6706     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6707     case MT_NONE:
6708     case MT_CHECK:
6709       break;
6710     case MT_CHECKMATE:
6711     case MT_STAINMATE:
6712       if (WhiteOnMove(currentMove)) {
6713         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6714       } else {
6715         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6716       }
6717       break;
6718     case MT_STALEMATE:
6719       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6720       break;
6721     }
6722     break;
6723
6724   case MachinePlaysBlack:
6725   case MachinePlaysWhite:
6726     /* disable certain menu options while machine is thinking */
6727     SetMachineThinkingEnables();
6728     break;
6729
6730   default:
6731     break;
6732   }
6733
6734   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6735   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6736
6737   if(bookHit) { // [HGM] book: simulate book reply
6738         static char bookMove[MSG_SIZ]; // a bit generous?
6739
6740         programStats.nodes = programStats.depth = programStats.time =
6741         programStats.score = programStats.got_only_move = 0;
6742         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6743
6744         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6745         strcat(bookMove, bookHit);
6746         HandleMachineMove(bookMove, &first);
6747   }
6748   return 1;
6749 }
6750
6751 void
6752 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6753      Board board;
6754      int flags;
6755      ChessMove kind;
6756      int rf, ff, rt, ft;
6757      VOIDSTAR closure;
6758 {
6759     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6760     Markers *m = (Markers *) closure;
6761     if(rf == fromY && ff == fromX)
6762         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6763                          || kind == WhiteCapturesEnPassant
6764                          || kind == BlackCapturesEnPassant);
6765     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6766 }
6767
6768 void
6769 MarkTargetSquares(int clear)
6770 {
6771   int x, y;
6772   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6773      !appData.testLegality || gameMode == EditPosition) return;
6774   if(clear) {
6775     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6776   } else {
6777     int capt = 0;
6778     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6779     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6780       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6781       if(capt)
6782       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6783     }
6784   }
6785   DrawPosition(TRUE, NULL);
6786 }
6787
6788 int
6789 Explode(Board board, int fromX, int fromY, int toX, int toY)
6790 {
6791     if(gameInfo.variant == VariantAtomic &&
6792        (board[toY][toX] != EmptySquare ||                     // capture?
6793         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6794                          board[fromY][fromX] == BlackPawn   )
6795       )) {
6796         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6797         return TRUE;
6798     }
6799     return FALSE;
6800 }
6801
6802 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6803
6804 int CanPromote(ChessSquare piece, int y)
6805 {
6806         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6807         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6808         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6809            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6810            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6811                                                   gameInfo.variant == VariantMakruk) return FALSE;
6812         return (piece == BlackPawn && y == 1 ||
6813                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6814                 piece == BlackLance && y == 1 ||
6815                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6816 }
6817
6818 void LeftClick(ClickType clickType, int xPix, int yPix)
6819 {
6820     int x, y;
6821     Boolean saveAnimate;
6822     static int second = 0, promotionChoice = 0, clearFlag = 0;
6823     char promoChoice = NULLCHAR;
6824     ChessSquare piece;
6825
6826     if(appData.seekGraph && appData.icsActive && loggedOn &&
6827         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6828         SeekGraphClick(clickType, xPix, yPix, 0);
6829         return;
6830     }
6831
6832     if (clickType == Press) ErrorPopDown();
6833
6834     x = EventToSquare(xPix, BOARD_WIDTH);
6835     y = EventToSquare(yPix, BOARD_HEIGHT);
6836     if (!flipView && y >= 0) {
6837         y = BOARD_HEIGHT - 1 - y;
6838     }
6839     if (flipView && x >= 0) {
6840         x = BOARD_WIDTH - 1 - x;
6841     }
6842
6843     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6844         defaultPromoChoice = promoSweep;
6845         promoSweep = EmptySquare;   // terminate sweep
6846         promoDefaultAltered = TRUE;
6847         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6848     }
6849
6850     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6851         if(clickType == Release) return; // ignore upclick of click-click destination
6852         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6853         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6854         if(gameInfo.holdingsWidth &&
6855                 (WhiteOnMove(currentMove)
6856                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6857                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6858             // click in right holdings, for determining promotion piece
6859             ChessSquare p = boards[currentMove][y][x];
6860             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6861             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6862             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6863                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6864                 fromX = fromY = -1;
6865                 return;
6866             }
6867         }
6868         DrawPosition(FALSE, boards[currentMove]);
6869         return;
6870     }
6871
6872     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6873     if(clickType == Press
6874             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6875               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6876               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6877         return;
6878
6879     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6880         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6881
6882     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6883         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6884                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6885         defaultPromoChoice = DefaultPromoChoice(side);
6886     }
6887
6888     autoQueen = appData.alwaysPromoteToQueen;
6889
6890     if (fromX == -1) {
6891       int originalY = y;
6892       gatingPiece = EmptySquare;
6893       if (clickType != Press) {
6894         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6895             DragPieceEnd(xPix, yPix); dragging = 0;
6896             DrawPosition(FALSE, NULL);
6897         }
6898         return;
6899       }
6900       fromX = x; fromY = y; toX = toY = -1;
6901       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6902          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6903          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6904             /* First square */
6905             if (OKToStartUserMove(fromX, fromY)) {
6906                 second = 0;
6907                 MarkTargetSquares(0);
6908                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6909                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6910                     promoSweep = defaultPromoChoice;
6911                     selectFlag = 0; lastX = xPix; lastY = yPix;
6912                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6913                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6914                 }
6915                 if (appData.highlightDragging) {
6916                     SetHighlights(fromX, fromY, -1, -1);
6917                 }
6918             } else fromX = fromY = -1;
6919             return;
6920         }
6921     }
6922
6923     /* fromX != -1 */
6924     if (clickType == Press && gameMode != EditPosition) {
6925         ChessSquare fromP;
6926         ChessSquare toP;
6927         int frc;
6928
6929         // ignore off-board to clicks
6930         if(y < 0 || x < 0) return;
6931
6932         /* Check if clicking again on the same color piece */
6933         fromP = boards[currentMove][fromY][fromX];
6934         toP = boards[currentMove][y][x];
6935         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6936         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6937              WhitePawn <= toP && toP <= WhiteKing &&
6938              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6939              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6940             (BlackPawn <= fromP && fromP <= BlackKing &&
6941              BlackPawn <= toP && toP <= BlackKing &&
6942              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6943              !(fromP == BlackKing && toP == BlackRook && frc))) {
6944             /* Clicked again on same color piece -- changed his mind */
6945             second = (x == fromX && y == fromY);
6946             promoDefaultAltered = FALSE;
6947             MarkTargetSquares(1);
6948            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6949             if (appData.highlightDragging) {
6950                 SetHighlights(x, y, -1, -1);
6951             } else {
6952                 ClearHighlights();
6953             }
6954             if (OKToStartUserMove(x, y)) {
6955                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6956                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6957                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6958                  gatingPiece = boards[currentMove][fromY][fromX];
6959                 else gatingPiece = EmptySquare;
6960                 fromX = x;
6961                 fromY = y; dragging = 1;
6962                 MarkTargetSquares(0);
6963                 DragPieceBegin(xPix, yPix, FALSE);
6964                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6965                     promoSweep = defaultPromoChoice;
6966                     selectFlag = 0; lastX = xPix; lastY = yPix;
6967                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6968                 }
6969             }
6970            }
6971            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6972            second = FALSE; 
6973         }
6974         // ignore clicks on holdings
6975         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6976     }
6977
6978     if (clickType == Release && x == fromX && y == fromY) {
6979         DragPieceEnd(xPix, yPix); dragging = 0;
6980         if(clearFlag) {
6981             // a deferred attempt to click-click move an empty square on top of a piece
6982             boards[currentMove][y][x] = EmptySquare;
6983             ClearHighlights();
6984             DrawPosition(FALSE, boards[currentMove]);
6985             fromX = fromY = -1; clearFlag = 0;
6986             return;
6987         }
6988         if (appData.animateDragging) {
6989             /* Undo animation damage if any */
6990             DrawPosition(FALSE, NULL);
6991         }
6992         if (second) {
6993             /* Second up/down in same square; just abort move */
6994             second = 0;
6995             fromX = fromY = -1;
6996             gatingPiece = EmptySquare;
6997             ClearHighlights();
6998             gotPremove = 0;
6999             ClearPremoveHighlights();
7000         } else {
7001             /* First upclick in same square; start click-click mode */
7002             SetHighlights(x, y, -1, -1);
7003         }
7004         return;
7005     }
7006
7007     clearFlag = 0;
7008
7009     /* we now have a different from- and (possibly off-board) to-square */
7010     /* Completed move */
7011     toX = x;
7012     toY = y;
7013     saveAnimate = appData.animate;
7014     MarkTargetSquares(1);
7015     if (clickType == Press) {
7016         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7017             // must be Edit Position mode with empty-square selected
7018             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7019             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7020             return;
7021         }
7022         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7023             ChessSquare piece = boards[currentMove][fromY][fromX];
7024             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7025             promoSweep = defaultPromoChoice;
7026             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7027             selectFlag = 0; lastX = xPix; lastY = yPix;
7028             Sweep(0); // Pawn that is going to promote: preview promotion piece
7029             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7030             DrawPosition(FALSE, boards[currentMove]);
7031             return;
7032         }
7033         /* Finish clickclick move */
7034         if (appData.animate || appData.highlightLastMove) {
7035             SetHighlights(fromX, fromY, toX, toY);
7036         } else {
7037             ClearHighlights();
7038         }
7039     } else {
7040         /* Finish drag move */
7041         if (appData.highlightLastMove) {
7042             SetHighlights(fromX, fromY, toX, toY);
7043         } else {
7044             ClearHighlights();
7045         }
7046         DragPieceEnd(xPix, yPix); dragging = 0;
7047         /* Don't animate move and drag both */
7048         appData.animate = FALSE;
7049     }
7050
7051     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7052     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7053         ChessSquare piece = boards[currentMove][fromY][fromX];
7054         if(gameMode == EditPosition && piece != EmptySquare &&
7055            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7056             int n;
7057
7058             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7059                 n = PieceToNumber(piece - (int)BlackPawn);
7060                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7061                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7063             } else
7064             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7065                 n = PieceToNumber(piece);
7066                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7067                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7068                 boards[currentMove][n][BOARD_WIDTH-2]++;
7069             }
7070             boards[currentMove][fromY][fromX] = EmptySquare;
7071         }
7072         ClearHighlights();
7073         fromX = fromY = -1;
7074         DrawPosition(TRUE, boards[currentMove]);
7075         return;
7076     }
7077
7078     // off-board moves should not be highlighted
7079     if(x < 0 || y < 0) ClearHighlights();
7080
7081     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7082
7083     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7084         SetHighlights(fromX, fromY, toX, toY);
7085         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7086             // [HGM] super: promotion to captured piece selected from holdings
7087             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7088             promotionChoice = TRUE;
7089             // kludge follows to temporarily execute move on display, without promoting yet
7090             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7091             boards[currentMove][toY][toX] = p;
7092             DrawPosition(FALSE, boards[currentMove]);
7093             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7094             boards[currentMove][toY][toX] = q;
7095             DisplayMessage("Click in holdings to choose piece", "");
7096             return;
7097         }
7098         PromotionPopUp();
7099     } else {
7100         int oldMove = currentMove;
7101         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7102         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7103         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7104         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7105            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7106             DrawPosition(TRUE, boards[currentMove]);
7107         fromX = fromY = -1;
7108     }
7109     appData.animate = saveAnimate;
7110     if (appData.animate || appData.animateDragging) {
7111         /* Undo animation damage if needed */
7112         DrawPosition(FALSE, NULL);
7113     }
7114 }
7115
7116 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7117 {   // front-end-free part taken out of PieceMenuPopup
7118     int whichMenu; int xSqr, ySqr;
7119
7120     if(seekGraphUp) { // [HGM] seekgraph
7121         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7122         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7123         return -2;
7124     }
7125
7126     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7127          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7128         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7129         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7130         if(action == Press)   {
7131             originalFlip = flipView;
7132             flipView = !flipView; // temporarily flip board to see game from partners perspective
7133             DrawPosition(TRUE, partnerBoard);
7134             DisplayMessage(partnerStatus, "");
7135             partnerUp = TRUE;
7136         } else if(action == Release) {
7137             flipView = originalFlip;
7138             DrawPosition(TRUE, boards[currentMove]);
7139             partnerUp = FALSE;
7140         }
7141         return -2;
7142     }
7143
7144     xSqr = EventToSquare(x, BOARD_WIDTH);
7145     ySqr = EventToSquare(y, BOARD_HEIGHT);
7146     if (action == Release) {
7147         if(pieceSweep != EmptySquare) {
7148             EditPositionMenuEvent(pieceSweep, toX, toY);
7149             pieceSweep = EmptySquare;
7150         } else UnLoadPV(); // [HGM] pv
7151     }
7152     if (action != Press) return -2; // return code to be ignored
7153     switch (gameMode) {
7154       case IcsExamining:
7155         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7156       case EditPosition:
7157         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7158         if (xSqr < 0 || ySqr < 0) return -1;
7159         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7160         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7161         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7162         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7163         NextPiece(0);
7164         return 2; // grab
7165       case IcsObserving:
7166         if(!appData.icsEngineAnalyze) return -1;
7167       case IcsPlayingWhite:
7168       case IcsPlayingBlack:
7169         if(!appData.zippyPlay) goto noZip;
7170       case AnalyzeMode:
7171       case AnalyzeFile:
7172       case MachinePlaysWhite:
7173       case MachinePlaysBlack:
7174       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7175         if (!appData.dropMenu) {
7176           LoadPV(x, y);
7177           return 2; // flag front-end to grab mouse events
7178         }
7179         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7180            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7181       case EditGame:
7182       noZip:
7183         if (xSqr < 0 || ySqr < 0) return -1;
7184         if (!appData.dropMenu || appData.testLegality &&
7185             gameInfo.variant != VariantBughouse &&
7186             gameInfo.variant != VariantCrazyhouse) return -1;
7187         whichMenu = 1; // drop menu
7188         break;
7189       default:
7190         return -1;
7191     }
7192
7193     if (((*fromX = xSqr) < 0) ||
7194         ((*fromY = ySqr) < 0)) {
7195         *fromX = *fromY = -1;
7196         return -1;
7197     }
7198     if (flipView)
7199       *fromX = BOARD_WIDTH - 1 - *fromX;
7200     else
7201       *fromY = BOARD_HEIGHT - 1 - *fromY;
7202
7203     return whichMenu;
7204 }
7205
7206 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7207 {
7208 //    char * hint = lastHint;
7209     FrontEndProgramStats stats;
7210
7211     stats.which = cps == &first ? 0 : 1;
7212     stats.depth = cpstats->depth;
7213     stats.nodes = cpstats->nodes;
7214     stats.score = cpstats->score;
7215     stats.time = cpstats->time;
7216     stats.pv = cpstats->movelist;
7217     stats.hint = lastHint;
7218     stats.an_move_index = 0;
7219     stats.an_move_count = 0;
7220
7221     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7222         stats.hint = cpstats->move_name;
7223         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7224         stats.an_move_count = cpstats->nr_moves;
7225     }
7226
7227     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
7228
7229     SetProgramStats( &stats );
7230 }
7231
7232 void
7233 ClearEngineOutputPane(int which)
7234 {
7235     static FrontEndProgramStats dummyStats;
7236     dummyStats.which = which;
7237     dummyStats.pv = "#";
7238     SetProgramStats( &dummyStats );
7239 }
7240
7241 #define MAXPLAYERS 500
7242
7243 char *
7244 TourneyStandings(int display)
7245 {
7246     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7247     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7248     char result, *p, *names[MAXPLAYERS];
7249
7250     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7251         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7252     names[0] = p = strdup(appData.participants);
7253     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7254
7255     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7256
7257     while(result = appData.results[nr]) {
7258         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7259         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7260         wScore = bScore = 0;
7261         switch(result) {
7262           case '+': wScore = 2; break;
7263           case '-': bScore = 2; break;
7264           case '=': wScore = bScore = 1; break;
7265           case ' ':
7266           case '*': return strdup("busy"); // tourney not finished
7267         }
7268         score[w] += wScore;
7269         score[b] += bScore;
7270         games[w]++;
7271         games[b]++;
7272         nr++;
7273     }
7274     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7275     for(w=0; w<nPlayers; w++) {
7276         bScore = -1;
7277         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7278         ranking[w] = b; points[w] = bScore; score[b] = -2;
7279     }
7280     p = malloc(nPlayers*34+1);
7281     for(w=0; w<nPlayers && w<display; w++)
7282         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7283     free(names[0]);
7284     return p;
7285 }
7286
7287 void
7288 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7289 {       // count all piece types
7290         int p, f, r;
7291         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7292         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7293         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7294                 p = board[r][f];
7295                 pCnt[p]++;
7296                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7297                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7298                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7299                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7300                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7301                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7302         }
7303 }
7304
7305 int
7306 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7307 {
7308         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7309         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7310
7311         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7312         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7313         if(myPawns == 2 && nMine == 3) // KPP
7314             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7315         if(myPawns == 1 && nMine == 2) // KP
7316             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7317         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7318             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7319         if(myPawns) return FALSE;
7320         if(pCnt[WhiteRook+side])
7321             return pCnt[BlackRook-side] ||
7322                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7323                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7324                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7325         if(pCnt[WhiteCannon+side]) {
7326             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7327             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7328         }
7329         if(pCnt[WhiteKnight+side])
7330             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7331         return FALSE;
7332 }
7333
7334 int
7335 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7336 {
7337         VariantClass v = gameInfo.variant;
7338
7339         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7340         if(v == VariantShatranj) return TRUE; // always winnable through baring
7341         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7342         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7343
7344         if(v == VariantXiangqi) {
7345                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7346
7347                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7348                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7349                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7350                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7351                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7352                 if(stale) // we have at least one last-rank P plus perhaps C
7353                     return majors // KPKX
7354                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7355                 else // KCA*E*
7356                     return pCnt[WhiteFerz+side] // KCAK
7357                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7358                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7359                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7360
7361         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7362                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7363
7364                 if(nMine == 1) return FALSE; // bare King
7365                 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
7366                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7367                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7368                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7369                 if(pCnt[WhiteKnight+side])
7370                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7371                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7372                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7373                 if(nBishops)
7374                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7375                 if(pCnt[WhiteAlfil+side])
7376                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7377                 if(pCnt[WhiteWazir+side])
7378                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7379         }
7380
7381         return TRUE;
7382 }
7383
7384 int
7385 CompareWithRights(Board b1, Board b2)
7386 {
7387     int rights = 0;
7388     if(!CompareBoards(b1, b2)) return FALSE;
7389     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7390     /* compare castling rights */
7391     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7392            rights++; /* King lost rights, while rook still had them */
7393     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7394         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7395            rights++; /* but at least one rook lost them */
7396     }
7397     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7398            rights++;
7399     if( b1[CASTLING][5] != NoRights ) {
7400         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7401            rights++;
7402     }
7403     return rights == 0;
7404 }
7405
7406 int
7407 Adjudicate(ChessProgramState *cps)
7408 {       // [HGM] some adjudications useful with buggy engines
7409         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7410         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7411         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7412         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7413         int k, count = 0; static int bare = 1;
7414         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7415         Boolean canAdjudicate = !appData.icsActive;
7416
7417         // most tests only when we understand the game, i.e. legality-checking on
7418             if( appData.testLegality )
7419             {   /* [HGM] Some more adjudications for obstinate engines */
7420                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7421                 static int moveCount = 6;
7422                 ChessMove result;
7423                 char *reason = NULL;
7424
7425                 /* Count what is on board. */
7426                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7427
7428                 /* Some material-based adjudications that have to be made before stalemate test */
7429                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7430                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7431                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7432                      if(canAdjudicate && appData.checkMates) {
7433                          if(engineOpponent)
7434                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7435                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7436                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7437                          return 1;
7438                      }
7439                 }
7440
7441                 /* Bare King in Shatranj (loses) or Losers (wins) */
7442                 if( nrW == 1 || nrB == 1) {
7443                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7444                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7445                      if(canAdjudicate && appData.checkMates) {
7446                          if(engineOpponent)
7447                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7448                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7449                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7450                          return 1;
7451                      }
7452                   } else
7453                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7454                   {    /* bare King */
7455                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7456                         if(canAdjudicate && appData.checkMates) {
7457                             /* but only adjudicate if adjudication enabled */
7458                             if(engineOpponent)
7459                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7460                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7461                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7462                             return 1;
7463                         }
7464                   }
7465                 } else bare = 1;
7466
7467
7468             // don't wait for engine to announce game end if we can judge ourselves
7469             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7470               case MT_CHECK:
7471                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7472                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7473                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7474                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7475                             checkCnt++;
7476                         if(checkCnt >= 2) {
7477                             reason = "Xboard adjudication: 3rd check";
7478                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7479                             break;
7480                         }
7481                     }
7482                 }
7483               case MT_NONE:
7484               default:
7485                 break;
7486               case MT_STALEMATE:
7487               case MT_STAINMATE:
7488                 reason = "Xboard adjudication: Stalemate";
7489                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7490                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7491                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7492                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7493                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7494                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7495                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7496                                                                         EP_CHECKMATE : EP_WINS);
7497                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7498                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7499                 }
7500                 break;
7501               case MT_CHECKMATE:
7502                 reason = "Xboard adjudication: Checkmate";
7503                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7504                 break;
7505             }
7506
7507                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7508                     case EP_STALEMATE:
7509                         result = GameIsDrawn; break;
7510                     case EP_CHECKMATE:
7511                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7512                     case EP_WINS:
7513                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7514                     default:
7515                         result = EndOfFile;
7516                 }
7517                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7518                     if(engineOpponent)
7519                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7520                     GameEnds( result, reason, GE_XBOARD );
7521                     return 1;
7522                 }
7523
7524                 /* Next absolutely insufficient mating material. */
7525                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7526                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7527                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7528
7529                      /* always flag draws, for judging claims */
7530                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7531
7532                      if(canAdjudicate && appData.materialDraws) {
7533                          /* but only adjudicate them if adjudication enabled */
7534                          if(engineOpponent) {
7535                            SendToProgram("force\n", engineOpponent); // suppress reply
7536                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7537                          }
7538                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7539                          return 1;
7540                      }
7541                 }
7542
7543                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7544                 if(gameInfo.variant == VariantXiangqi ?
7545                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7546                  : nrW + nrB == 4 &&
7547                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7548                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7549                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7550                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7551                    ) ) {
7552                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7553                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7554                           if(engineOpponent) {
7555                             SendToProgram("force\n", engineOpponent); // suppress reply
7556                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7557                           }
7558                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7559                           return 1;
7560                      }
7561                 } else moveCount = 6;
7562             }
7563         if (appData.debugMode) { int i;
7564             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7565                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7566                     appData.drawRepeats);
7567             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7568               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7569
7570         }
7571
7572         // Repetition draws and 50-move rule can be applied independently of legality testing
7573
7574                 /* Check for rep-draws */
7575                 count = 0;
7576                 for(k = forwardMostMove-2;
7577                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7578                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7579                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7580                     k-=2)
7581                 {   int rights=0;
7582                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7583                         /* compare castling rights */
7584                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7585                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7586                                 rights++; /* King lost rights, while rook still had them */
7587                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7588                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7589                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7590                                    rights++; /* but at least one rook lost them */
7591                         }
7592                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7593                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7594                                 rights++;
7595                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7596                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7597                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7598                                    rights++;
7599                         }
7600                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7601                             && appData.drawRepeats > 1) {
7602                              /* adjudicate after user-specified nr of repeats */
7603                              int result = GameIsDrawn;
7604                              char *details = "XBoard adjudication: repetition draw";
7605                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7606                                 // [HGM] xiangqi: check for forbidden perpetuals
7607                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7608                                 for(m=forwardMostMove; m>k; m-=2) {
7609                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7610                                         ourPerpetual = 0; // the current mover did not always check
7611                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7612                                         hisPerpetual = 0; // the opponent did not always check
7613                                 }
7614                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7615                                                                         ourPerpetual, hisPerpetual);
7616                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7617                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7618                                     details = "Xboard adjudication: perpetual checking";
7619                                 } else
7620                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7621                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7622                                 } else
7623                                 // Now check for perpetual chases
7624                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7625                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7626                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7627                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7628                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7629                                         details = "Xboard adjudication: perpetual chasing";
7630                                     } else
7631                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7632                                         break; // Abort repetition-checking loop.
7633                                 }
7634                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7635                              }
7636                              if(engineOpponent) {
7637                                SendToProgram("force\n", engineOpponent); // suppress reply
7638                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7639                              }
7640                              GameEnds( result, details, GE_XBOARD );
7641                              return 1;
7642                         }
7643                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7644                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7645                     }
7646                 }
7647
7648                 /* Now we test for 50-move draws. Determine ply count */
7649                 count = forwardMostMove;
7650                 /* look for last irreversble move */
7651                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7652                     count--;
7653                 /* if we hit starting position, add initial plies */
7654                 if( count == backwardMostMove )
7655                     count -= initialRulePlies;
7656                 count = forwardMostMove - count;
7657                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7658                         // adjust reversible move counter for checks in Xiangqi
7659                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7660                         if(i < backwardMostMove) i = backwardMostMove;
7661                         while(i <= forwardMostMove) {
7662                                 lastCheck = inCheck; // check evasion does not count
7663                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7664                                 if(inCheck || lastCheck) count--; // check does not count
7665                                 i++;
7666                         }
7667                 }
7668                 if( count >= 100)
7669                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7670                          /* this is used to judge if draw claims are legal */
7671                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7672                          if(engineOpponent) {
7673                            SendToProgram("force\n", engineOpponent); // suppress reply
7674                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7675                          }
7676                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7677                          return 1;
7678                 }
7679
7680                 /* if draw offer is pending, treat it as a draw claim
7681                  * when draw condition present, to allow engines a way to
7682                  * claim draws before making their move to avoid a race
7683                  * condition occurring after their move
7684                  */
7685                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7686                          char *p = NULL;
7687                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7688                              p = "Draw claim: 50-move rule";
7689                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7690                              p = "Draw claim: 3-fold repetition";
7691                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7692                              p = "Draw claim: insufficient mating material";
7693                          if( p != NULL && canAdjudicate) {
7694                              if(engineOpponent) {
7695                                SendToProgram("force\n", engineOpponent); // suppress reply
7696                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7697                              }
7698                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7699                              return 1;
7700                          }
7701                 }
7702
7703                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7704                     if(engineOpponent) {
7705                       SendToProgram("force\n", engineOpponent); // suppress reply
7706                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7707                     }
7708                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7709                     return 1;
7710                 }
7711         return 0;
7712 }
7713
7714 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7715 {   // [HGM] book: this routine intercepts moves to simulate book replies
7716     char *bookHit = NULL;
7717
7718     //first determine if the incoming move brings opponent into his book
7719     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7720         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7721     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7722     if(bookHit != NULL && !cps->bookSuspend) {
7723         // make sure opponent is not going to reply after receiving move to book position
7724         SendToProgram("force\n", cps);
7725         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7726     }
7727     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7728     // now arrange restart after book miss
7729     if(bookHit) {
7730         // after a book hit we never send 'go', and the code after the call to this routine
7731         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7732         char buf[MSG_SIZ], *move = bookHit;
7733         if(cps->useSAN) {
7734             int fromX, fromY, toX, toY;
7735             char promoChar;
7736             ChessMove moveType;
7737             move = buf + 30;
7738             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7739                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7740                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7741                                     PosFlags(forwardMostMove),
7742                                     fromY, fromX, toY, toX, promoChar, move);
7743             } else {
7744                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7745                 bookHit = NULL;
7746             }
7747         }
7748         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7749         SendToProgram(buf, cps);
7750         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7751     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7752         SendToProgram("go\n", cps);
7753         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7754     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7755         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7756             SendToProgram("go\n", cps);
7757         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7758     }
7759     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7760 }
7761
7762 char *savedMessage;
7763 ChessProgramState *savedState;
7764 void DeferredBookMove(void)
7765 {
7766         if(savedState->lastPing != savedState->lastPong)
7767                     ScheduleDelayedEvent(DeferredBookMove, 10);
7768         else
7769         HandleMachineMove(savedMessage, savedState);
7770 }
7771
7772 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7773
7774 void
7775 HandleMachineMove(message, cps)
7776      char *message;
7777      ChessProgramState *cps;
7778 {
7779     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7780     char realname[MSG_SIZ];
7781     int fromX, fromY, toX, toY;
7782     ChessMove moveType;
7783     char promoChar;
7784     char *p, *pv=buf1;
7785     int machineWhite;
7786     char *bookHit;
7787
7788     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7789         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7790         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7791             DisplayError(_("Invalid pairing from pairing engine"), 0);
7792             return;
7793         }
7794         pairingReceived = 1;
7795         NextMatchGame();
7796         return; // Skim the pairing messages here.
7797     }
7798
7799     cps->userError = 0;
7800
7801 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7802     /*
7803      * Kludge to ignore BEL characters
7804      */
7805     while (*message == '\007') message++;
7806
7807     /*
7808      * [HGM] engine debug message: ignore lines starting with '#' character
7809      */
7810     if(cps->debug && *message == '#') return;
7811
7812     /*
7813      * Look for book output
7814      */
7815     if (cps == &first && bookRequested) {
7816         if (message[0] == '\t' || message[0] == ' ') {
7817             /* Part of the book output is here; append it */
7818             strcat(bookOutput, message);
7819             strcat(bookOutput, "  \n");
7820             return;
7821         } else if (bookOutput[0] != NULLCHAR) {
7822             /* All of book output has arrived; display it */
7823             char *p = bookOutput;
7824             while (*p != NULLCHAR) {
7825                 if (*p == '\t') *p = ' ';
7826                 p++;
7827             }
7828             DisplayInformation(bookOutput);
7829             bookRequested = FALSE;
7830             /* Fall through to parse the current output */
7831         }
7832     }
7833
7834     /*
7835      * Look for machine move.
7836      */
7837     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7838         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7839     {
7840         /* This method is only useful on engines that support ping */
7841         if (cps->lastPing != cps->lastPong) {
7842           if (gameMode == BeginningOfGame) {
7843             /* Extra move from before last new; ignore */
7844             if (appData.debugMode) {
7845                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7846             }
7847           } else {
7848             if (appData.debugMode) {
7849                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7850                         cps->which, gameMode);
7851             }
7852
7853             SendToProgram("undo\n", cps);
7854           }
7855           return;
7856         }
7857
7858         switch (gameMode) {
7859           case BeginningOfGame:
7860             /* Extra move from before last reset; ignore */
7861             if (appData.debugMode) {
7862                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7863             }
7864             return;
7865
7866           case EndOfGame:
7867           case IcsIdle:
7868           default:
7869             /* Extra move after we tried to stop.  The mode test is
7870                not a reliable way of detecting this problem, but it's
7871                the best we can do on engines that don't support ping.
7872             */
7873             if (appData.debugMode) {
7874                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7875                         cps->which, gameMode);
7876             }
7877             SendToProgram("undo\n", cps);
7878             return;
7879
7880           case MachinePlaysWhite:
7881           case IcsPlayingWhite:
7882             machineWhite = TRUE;
7883             break;
7884
7885           case MachinePlaysBlack:
7886           case IcsPlayingBlack:
7887             machineWhite = FALSE;
7888             break;
7889
7890           case TwoMachinesPlay:
7891             machineWhite = (cps->twoMachinesColor[0] == 'w');
7892             break;
7893         }
7894         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7895             if (appData.debugMode) {
7896                 fprintf(debugFP,
7897                         "Ignoring move out of turn by %s, gameMode %d"
7898                         ", forwardMost %d\n",
7899                         cps->which, gameMode, forwardMostMove);
7900             }
7901             return;
7902         }
7903
7904     if (appData.debugMode) { int f = forwardMostMove;
7905         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7906                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7907                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7908     }
7909         if(cps->alphaRank) AlphaRank(machineMove, 4);
7910         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7911                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7912             /* Machine move could not be parsed; ignore it. */
7913           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7914                     machineMove, _(cps->which));
7915             DisplayError(buf1, 0);
7916             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7917                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7918             if (gameMode == TwoMachinesPlay) {
7919               GameEnds(machineWhite ? BlackWins : WhiteWins,
7920                        buf1, GE_XBOARD);
7921             }
7922             return;
7923         }
7924
7925         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7926         /* So we have to redo legality test with true e.p. status here,  */
7927         /* to make sure an illegal e.p. capture does not slip through,   */
7928         /* to cause a forfeit on a justified illegal-move complaint      */
7929         /* of the opponent.                                              */
7930         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7931            ChessMove moveType;
7932            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7933                              fromY, fromX, toY, toX, promoChar);
7934             if (appData.debugMode) {
7935                 int i;
7936                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7937                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7938                 fprintf(debugFP, "castling rights\n");
7939             }
7940             if(moveType == IllegalMove) {
7941               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7942                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7943                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7944                            buf1, GE_XBOARD);
7945                 return;
7946            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7947            /* [HGM] Kludge to handle engines that send FRC-style castling
7948               when they shouldn't (like TSCP-Gothic) */
7949            switch(moveType) {
7950              case WhiteASideCastleFR:
7951              case BlackASideCastleFR:
7952                toX+=2;
7953                currentMoveString[2]++;
7954                break;
7955              case WhiteHSideCastleFR:
7956              case BlackHSideCastleFR:
7957                toX--;
7958                currentMoveString[2]--;
7959                break;
7960              default: ; // nothing to do, but suppresses warning of pedantic compilers
7961            }
7962         }
7963         hintRequested = FALSE;
7964         lastHint[0] = NULLCHAR;
7965         bookRequested = FALSE;
7966         /* Program may be pondering now */
7967         cps->maybeThinking = TRUE;
7968         if (cps->sendTime == 2) cps->sendTime = 1;
7969         if (cps->offeredDraw) cps->offeredDraw--;
7970
7971         /* [AS] Save move info*/
7972         pvInfoList[ forwardMostMove ].score = programStats.score;
7973         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7974         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7975
7976         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7977
7978         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7979         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7980             int count = 0;
7981
7982             while( count < adjudicateLossPlies ) {
7983                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7984
7985                 if( count & 1 ) {
7986                     score = -score; /* Flip score for winning side */
7987                 }
7988
7989                 if( score > adjudicateLossThreshold ) {
7990                     break;
7991                 }
7992
7993                 count++;
7994             }
7995
7996             if( count >= adjudicateLossPlies ) {
7997                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7998
7999                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8000                     "Xboard adjudication",
8001                     GE_XBOARD );
8002
8003                 return;
8004             }
8005         }
8006
8007         if(Adjudicate(cps)) {
8008             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8009             return; // [HGM] adjudicate: for all automatic game ends
8010         }
8011
8012 #if ZIPPY
8013         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8014             first.initDone) {
8015           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8016                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8017                 SendToICS("draw ");
8018                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8019           }
8020           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8021           ics_user_moved = 1;
8022           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8023                 char buf[3*MSG_SIZ];
8024
8025                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8026                         programStats.score / 100.,
8027                         programStats.depth,
8028                         programStats.time / 100.,
8029                         (unsigned int)programStats.nodes,
8030                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8031                         programStats.movelist);
8032                 SendToICS(buf);
8033 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8034           }
8035         }
8036 #endif
8037
8038         /* [AS] Clear stats for next move */
8039         ClearProgramStats();
8040         thinkOutput[0] = NULLCHAR;
8041         hiddenThinkOutputState = 0;
8042
8043         bookHit = NULL;
8044         if (gameMode == TwoMachinesPlay) {
8045             /* [HGM] relaying draw offers moved to after reception of move */
8046             /* and interpreting offer as claim if it brings draw condition */
8047             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8048                 SendToProgram("draw\n", cps->other);
8049             }
8050             if (cps->other->sendTime) {
8051                 SendTimeRemaining(cps->other,
8052                                   cps->other->twoMachinesColor[0] == 'w');
8053             }
8054             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8055             if (firstMove && !bookHit) {
8056                 firstMove = FALSE;
8057                 if (cps->other->useColors) {
8058                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8059                 }
8060                 SendToProgram("go\n", cps->other);
8061             }
8062             cps->other->maybeThinking = TRUE;
8063         }
8064
8065         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8066
8067         if (!pausing && appData.ringBellAfterMoves) {
8068             RingBell();
8069         }
8070
8071         /*
8072          * Reenable menu items that were disabled while
8073          * machine was thinking
8074          */
8075         if (gameMode != TwoMachinesPlay)
8076             SetUserThinkingEnables();
8077
8078         // [HGM] book: after book hit opponent has received move and is now in force mode
8079         // force the book reply into it, and then fake that it outputted this move by jumping
8080         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8081         if(bookHit) {
8082                 static char bookMove[MSG_SIZ]; // a bit generous?
8083
8084                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8085                 strcat(bookMove, bookHit);
8086                 message = bookMove;
8087                 cps = cps->other;
8088                 programStats.nodes = programStats.depth = programStats.time =
8089                 programStats.score = programStats.got_only_move = 0;
8090                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8091
8092                 if(cps->lastPing != cps->lastPong) {
8093                     savedMessage = message; // args for deferred call
8094                     savedState = cps;
8095                     ScheduleDelayedEvent(DeferredBookMove, 10);
8096                     return;
8097                 }
8098                 goto FakeBookMove;
8099         }
8100
8101         return;
8102     }
8103
8104     /* Set special modes for chess engines.  Later something general
8105      *  could be added here; for now there is just one kludge feature,
8106      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8107      *  when "xboard" is given as an interactive command.
8108      */
8109     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8110         cps->useSigint = FALSE;
8111         cps->useSigterm = FALSE;
8112     }
8113     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8114       ParseFeatures(message+8, cps);
8115       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8116     }
8117
8118     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8119       int dummy, s=6; char buf[MSG_SIZ];
8120       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8121       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8122       ParseFEN(boards[0], &dummy, message+s);
8123       DrawPosition(TRUE, boards[0]);
8124       startedFromSetupPosition = TRUE;
8125       return;
8126     }
8127     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8128      * want this, I was asked to put it in, and obliged.
8129      */
8130     if (!strncmp(message, "setboard ", 9)) {
8131         Board initial_position;
8132
8133         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8134
8135         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8136             DisplayError(_("Bad FEN received from engine"), 0);
8137             return ;
8138         } else {
8139            Reset(TRUE, FALSE);
8140            CopyBoard(boards[0], initial_position);
8141            initialRulePlies = FENrulePlies;
8142            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8143            else gameMode = MachinePlaysBlack;
8144            DrawPosition(FALSE, boards[currentMove]);
8145         }
8146         return;
8147     }
8148
8149     /*
8150      * Look for communication commands
8151      */
8152     if (!strncmp(message, "telluser ", 9)) {
8153         if(message[9] == '\\' && message[10] == '\\')
8154             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8155         PlayTellSound();
8156         DisplayNote(message + 9);
8157         return;
8158     }
8159     if (!strncmp(message, "tellusererror ", 14)) {
8160         cps->userError = 1;
8161         if(message[14] == '\\' && message[15] == '\\')
8162             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8163         PlayTellSound();
8164         DisplayError(message + 14, 0);
8165         return;
8166     }
8167     if (!strncmp(message, "tellopponent ", 13)) {
8168       if (appData.icsActive) {
8169         if (loggedOn) {
8170           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8171           SendToICS(buf1);
8172         }
8173       } else {
8174         DisplayNote(message + 13);
8175       }
8176       return;
8177     }
8178     if (!strncmp(message, "tellothers ", 11)) {
8179       if (appData.icsActive) {
8180         if (loggedOn) {
8181           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8182           SendToICS(buf1);
8183         }
8184       }
8185       return;
8186     }
8187     if (!strncmp(message, "tellall ", 8)) {
8188       if (appData.icsActive) {
8189         if (loggedOn) {
8190           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8191           SendToICS(buf1);
8192         }
8193       } else {
8194         DisplayNote(message + 8);
8195       }
8196       return;
8197     }
8198     if (strncmp(message, "warning", 7) == 0) {
8199         /* Undocumented feature, use tellusererror in new code */
8200         DisplayError(message, 0);
8201         return;
8202     }
8203     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8204         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8205         strcat(realname, " query");
8206         AskQuestion(realname, buf2, buf1, cps->pr);
8207         return;
8208     }
8209     /* Commands from the engine directly to ICS.  We don't allow these to be
8210      *  sent until we are logged on. Crafty kibitzes have been known to
8211      *  interfere with the login process.
8212      */
8213     if (loggedOn) {
8214         if (!strncmp(message, "tellics ", 8)) {
8215             SendToICS(message + 8);
8216             SendToICS("\n");
8217             return;
8218         }
8219         if (!strncmp(message, "tellicsnoalias ", 15)) {
8220             SendToICS(ics_prefix);
8221             SendToICS(message + 15);
8222             SendToICS("\n");
8223             return;
8224         }
8225         /* The following are for backward compatibility only */
8226         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8227             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8228             SendToICS(ics_prefix);
8229             SendToICS(message);
8230             SendToICS("\n");
8231             return;
8232         }
8233     }
8234     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8235         return;
8236     }
8237     /*
8238      * If the move is illegal, cancel it and redraw the board.
8239      * Also deal with other error cases.  Matching is rather loose
8240      * here to accommodate engines written before the spec.
8241      */
8242     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8243         strncmp(message, "Error", 5) == 0) {
8244         if (StrStr(message, "name") ||
8245             StrStr(message, "rating") || StrStr(message, "?") ||
8246             StrStr(message, "result") || StrStr(message, "board") ||
8247             StrStr(message, "bk") || StrStr(message, "computer") ||
8248             StrStr(message, "variant") || StrStr(message, "hint") ||
8249             StrStr(message, "random") || StrStr(message, "depth") ||
8250             StrStr(message, "accepted")) {
8251             return;
8252         }
8253         if (StrStr(message, "protover")) {
8254           /* Program is responding to input, so it's apparently done
8255              initializing, and this error message indicates it is
8256              protocol version 1.  So we don't need to wait any longer
8257              for it to initialize and send feature commands. */
8258           FeatureDone(cps, 1);
8259           cps->protocolVersion = 1;
8260           return;
8261         }
8262         cps->maybeThinking = FALSE;
8263
8264         if (StrStr(message, "draw")) {
8265             /* Program doesn't have "draw" command */
8266             cps->sendDrawOffers = 0;
8267             return;
8268         }
8269         if (cps->sendTime != 1 &&
8270             (StrStr(message, "time") || StrStr(message, "otim"))) {
8271           /* Program apparently doesn't have "time" or "otim" command */
8272           cps->sendTime = 0;
8273           return;
8274         }
8275         if (StrStr(message, "analyze")) {
8276             cps->analysisSupport = FALSE;
8277             cps->analyzing = FALSE;
8278 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8279             EditGameEvent(); // [HGM] try to preserve loaded game
8280             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8281             DisplayError(buf2, 0);
8282             return;
8283         }
8284         if (StrStr(message, "(no matching move)st")) {
8285           /* Special kludge for GNU Chess 4 only */
8286           cps->stKludge = TRUE;
8287           SendTimeControl(cps, movesPerSession, timeControl,
8288                           timeIncrement, appData.searchDepth,
8289                           searchTime);
8290           return;
8291         }
8292         if (StrStr(message, "(no matching move)sd")) {
8293           /* Special kludge for GNU Chess 4 only */
8294           cps->sdKludge = TRUE;
8295           SendTimeControl(cps, movesPerSession, timeControl,
8296                           timeIncrement, appData.searchDepth,
8297                           searchTime);
8298           return;
8299         }
8300         if (!StrStr(message, "llegal")) {
8301             return;
8302         }
8303         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8304             gameMode == IcsIdle) return;
8305         if (forwardMostMove <= backwardMostMove) return;
8306         if (pausing) PauseEvent();
8307       if(appData.forceIllegal) {
8308             // [HGM] illegal: machine refused move; force position after move into it
8309           SendToProgram("force\n", cps);
8310           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8311                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8312                 // when black is to move, while there might be nothing on a2 or black
8313                 // might already have the move. So send the board as if white has the move.
8314                 // But first we must change the stm of the engine, as it refused the last move
8315                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8316                 if(WhiteOnMove(forwardMostMove)) {
8317                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8318                     SendBoard(cps, forwardMostMove); // kludgeless board
8319                 } else {
8320                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8321                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8322                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8323                 }
8324           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8325             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8326                  gameMode == TwoMachinesPlay)
8327               SendToProgram("go\n", cps);
8328             return;
8329       } else
8330         if (gameMode == PlayFromGameFile) {
8331             /* Stop reading this game file */
8332             gameMode = EditGame;
8333             ModeHighlight();
8334         }
8335         /* [HGM] illegal-move claim should forfeit game when Xboard */
8336         /* only passes fully legal moves                            */
8337         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8338             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8339                                 "False illegal-move claim", GE_XBOARD );
8340             return; // do not take back move we tested as valid
8341         }
8342         currentMove = forwardMostMove-1;
8343         DisplayMove(currentMove-1); /* before DisplayMoveError */
8344         SwitchClocks(forwardMostMove-1); // [HGM] race
8345         DisplayBothClocks();
8346         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8347                 parseList[currentMove], _(cps->which));
8348         DisplayMoveError(buf1);
8349         DrawPosition(FALSE, boards[currentMove]);
8350         return;
8351     }
8352     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8353         /* Program has a broken "time" command that
8354            outputs a string not ending in newline.
8355            Don't use it. */
8356         cps->sendTime = 0;
8357     }
8358
8359     /*
8360      * If chess program startup fails, exit with an error message.
8361      * Attempts to recover here are futile.
8362      */
8363     if ((StrStr(message, "unknown host") != NULL)
8364         || (StrStr(message, "No remote directory") != NULL)
8365         || (StrStr(message, "not found") != NULL)
8366         || (StrStr(message, "No such file") != NULL)
8367         || (StrStr(message, "can't alloc") != NULL)
8368         || (StrStr(message, "Permission denied") != NULL)) {
8369
8370         cps->maybeThinking = FALSE;
8371         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8372                 _(cps->which), cps->program, cps->host, message);
8373         RemoveInputSource(cps->isr);
8374         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8375             if(cps == &first) appData.noChessProgram = TRUE;
8376             DisplayError(buf1, 0);
8377         }
8378         return;
8379     }
8380
8381     /*
8382      * Look for hint output
8383      */
8384     if (sscanf(message, "Hint: %s", buf1) == 1) {
8385         if (cps == &first && hintRequested) {
8386             hintRequested = FALSE;
8387             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8388                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8389                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8390                                     PosFlags(forwardMostMove),
8391                                     fromY, fromX, toY, toX, promoChar, buf1);
8392                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8393                 DisplayInformation(buf2);
8394             } else {
8395                 /* Hint move could not be parsed!? */
8396               snprintf(buf2, sizeof(buf2),
8397                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8398                         buf1, _(cps->which));
8399                 DisplayError(buf2, 0);
8400             }
8401         } else {
8402           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8403         }
8404         return;
8405     }
8406
8407     /*
8408      * Ignore other messages if game is not in progress
8409      */
8410     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8411         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8412
8413     /*
8414      * look for win, lose, draw, or draw offer
8415      */
8416     if (strncmp(message, "1-0", 3) == 0) {
8417         char *p, *q, *r = "";
8418         p = strchr(message, '{');
8419         if (p) {
8420             q = strchr(p, '}');
8421             if (q) {
8422                 *q = NULLCHAR;
8423                 r = p + 1;
8424             }
8425         }
8426         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8427         return;
8428     } else if (strncmp(message, "0-1", 3) == 0) {
8429         char *p, *q, *r = "";
8430         p = strchr(message, '{');
8431         if (p) {
8432             q = strchr(p, '}');
8433             if (q) {
8434                 *q = NULLCHAR;
8435                 r = p + 1;
8436             }
8437         }
8438         /* Kludge for Arasan 4.1 bug */
8439         if (strcmp(r, "Black resigns") == 0) {
8440             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8441             return;
8442         }
8443         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8444         return;
8445     } else if (strncmp(message, "1/2", 3) == 0) {
8446         char *p, *q, *r = "";
8447         p = strchr(message, '{');
8448         if (p) {
8449             q = strchr(p, '}');
8450             if (q) {
8451                 *q = NULLCHAR;
8452                 r = p + 1;
8453             }
8454         }
8455
8456         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8457         return;
8458
8459     } else if (strncmp(message, "White resign", 12) == 0) {
8460         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8461         return;
8462     } else if (strncmp(message, "Black resign", 12) == 0) {
8463         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8464         return;
8465     } else if (strncmp(message, "White matches", 13) == 0 ||
8466                strncmp(message, "Black matches", 13) == 0   ) {
8467         /* [HGM] ignore GNUShogi noises */
8468         return;
8469     } else if (strncmp(message, "White", 5) == 0 &&
8470                message[5] != '(' &&
8471                StrStr(message, "Black") == NULL) {
8472         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8473         return;
8474     } else if (strncmp(message, "Black", 5) == 0 &&
8475                message[5] != '(') {
8476         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8477         return;
8478     } else if (strcmp(message, "resign") == 0 ||
8479                strcmp(message, "computer resigns") == 0) {
8480         switch (gameMode) {
8481           case MachinePlaysBlack:
8482           case IcsPlayingBlack:
8483             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8484             break;
8485           case MachinePlaysWhite:
8486           case IcsPlayingWhite:
8487             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8488             break;
8489           case TwoMachinesPlay:
8490             if (cps->twoMachinesColor[0] == 'w')
8491               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8492             else
8493               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8494             break;
8495           default:
8496             /* can't happen */
8497             break;
8498         }
8499         return;
8500     } else if (strncmp(message, "opponent mates", 14) == 0) {
8501         switch (gameMode) {
8502           case MachinePlaysBlack:
8503           case IcsPlayingBlack:
8504             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8505             break;
8506           case MachinePlaysWhite:
8507           case IcsPlayingWhite:
8508             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8509             break;
8510           case TwoMachinesPlay:
8511             if (cps->twoMachinesColor[0] == 'w')
8512               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8513             else
8514               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8515             break;
8516           default:
8517             /* can't happen */
8518             break;
8519         }
8520         return;
8521     } else if (strncmp(message, "computer mates", 14) == 0) {
8522         switch (gameMode) {
8523           case MachinePlaysBlack:
8524           case IcsPlayingBlack:
8525             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8526             break;
8527           case MachinePlaysWhite:
8528           case IcsPlayingWhite:
8529             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8530             break;
8531           case TwoMachinesPlay:
8532             if (cps->twoMachinesColor[0] == 'w')
8533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8534             else
8535               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8536             break;
8537           default:
8538             /* can't happen */
8539             break;
8540         }
8541         return;
8542     } else if (strncmp(message, "checkmate", 9) == 0) {
8543         if (WhiteOnMove(forwardMostMove)) {
8544             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8545         } else {
8546             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8547         }
8548         return;
8549     } else if (strstr(message, "Draw") != NULL ||
8550                strstr(message, "game is a draw") != NULL) {
8551         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8552         return;
8553     } else if (strstr(message, "offer") != NULL &&
8554                strstr(message, "draw") != NULL) {
8555 #if ZIPPY
8556         if (appData.zippyPlay && first.initDone) {
8557             /* Relay offer to ICS */
8558             SendToICS(ics_prefix);
8559             SendToICS("draw\n");
8560         }
8561 #endif
8562         cps->offeredDraw = 2; /* valid until this engine moves twice */
8563         if (gameMode == TwoMachinesPlay) {
8564             if (cps->other->offeredDraw) {
8565                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8566             /* [HGM] in two-machine mode we delay relaying draw offer      */
8567             /* until after we also have move, to see if it is really claim */
8568             }
8569         } else if (gameMode == MachinePlaysWhite ||
8570                    gameMode == MachinePlaysBlack) {
8571           if (userOfferedDraw) {
8572             DisplayInformation(_("Machine accepts your draw offer"));
8573             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8574           } else {
8575             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8576           }
8577         }
8578     }
8579
8580
8581     /*
8582      * Look for thinking output
8583      */
8584     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8585           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8586                                 ) {
8587         int plylev, mvleft, mvtot, curscore, time;
8588         char mvname[MOVE_LEN];
8589         u64 nodes; // [DM]
8590         char plyext;
8591         int ignore = FALSE;
8592         int prefixHint = FALSE;
8593         mvname[0] = NULLCHAR;
8594
8595         switch (gameMode) {
8596           case MachinePlaysBlack:
8597           case IcsPlayingBlack:
8598             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8599             break;
8600           case MachinePlaysWhite:
8601           case IcsPlayingWhite:
8602             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8603             break;
8604           case AnalyzeMode:
8605           case AnalyzeFile:
8606             break;
8607           case IcsObserving: /* [DM] icsEngineAnalyze */
8608             if (!appData.icsEngineAnalyze) ignore = TRUE;
8609             break;
8610           case TwoMachinesPlay:
8611             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8612                 ignore = TRUE;
8613             }
8614             break;
8615           default:
8616             ignore = TRUE;
8617             break;
8618         }
8619
8620         if (!ignore) {
8621             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8622             buf1[0] = NULLCHAR;
8623             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8624                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8625
8626                 if (plyext != ' ' && plyext != '\t') {
8627                     time *= 100;
8628                 }
8629
8630                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8631                 if( cps->scoreIsAbsolute &&
8632                     ( gameMode == MachinePlaysBlack ||
8633                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8634                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8635                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8636                      !WhiteOnMove(currentMove)
8637                     ) )
8638                 {
8639                     curscore = -curscore;
8640                 }
8641
8642                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8643
8644                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8645                         char buf[MSG_SIZ];
8646                         FILE *f;
8647                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8648                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8649                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8650                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8651                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8652                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8653                                 fclose(f);
8654                         } else DisplayError("failed writing PV", 0);
8655                 }
8656
8657                 tempStats.depth = plylev;
8658                 tempStats.nodes = nodes;
8659                 tempStats.time = time;
8660                 tempStats.score = curscore;
8661                 tempStats.got_only_move = 0;
8662
8663                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8664                         int ticklen;
8665
8666                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8667                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8668                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8669                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8670                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8671                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8672                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8673                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8674                 }
8675
8676                 /* Buffer overflow protection */
8677                 if (pv[0] != NULLCHAR) {
8678                     if (strlen(pv) >= sizeof(tempStats.movelist)
8679                         && appData.debugMode) {
8680                         fprintf(debugFP,
8681                                 "PV is too long; using the first %u bytes.\n",
8682                                 (unsigned) sizeof(tempStats.movelist) - 1);
8683                     }
8684
8685                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8686                 } else {
8687                     sprintf(tempStats.movelist, " no PV\n");
8688                 }
8689
8690                 if (tempStats.seen_stat) {
8691                     tempStats.ok_to_send = 1;
8692                 }
8693
8694                 if (strchr(tempStats.movelist, '(') != NULL) {
8695                     tempStats.line_is_book = 1;
8696                     tempStats.nr_moves = 0;
8697                     tempStats.moves_left = 0;
8698                 } else {
8699                     tempStats.line_is_book = 0;
8700                 }
8701
8702                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8703                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8704
8705                 SendProgramStatsToFrontend( cps, &tempStats );
8706
8707                 /*
8708                     [AS] Protect the thinkOutput buffer from overflow... this
8709                     is only useful if buf1 hasn't overflowed first!
8710                 */
8711                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8712                          plylev,
8713                          (gameMode == TwoMachinesPlay ?
8714                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8715                          ((double) curscore) / 100.0,
8716                          prefixHint ? lastHint : "",
8717                          prefixHint ? " " : "" );
8718
8719                 if( buf1[0] != NULLCHAR ) {
8720                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8721
8722                     if( strlen(pv) > max_len ) {
8723                         if( appData.debugMode) {
8724                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8725                         }
8726                         pv[max_len+1] = '\0';
8727                     }
8728
8729                     strcat( thinkOutput, pv);
8730                 }
8731
8732                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8733                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8734                     DisplayMove(currentMove - 1);
8735                 }
8736                 return;
8737
8738             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8739                 /* crafty (9.25+) says "(only move) <move>"
8740                  * if there is only 1 legal move
8741                  */
8742                 sscanf(p, "(only move) %s", buf1);
8743                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8744                 sprintf(programStats.movelist, "%s (only move)", buf1);
8745                 programStats.depth = 1;
8746                 programStats.nr_moves = 1;
8747                 programStats.moves_left = 1;
8748                 programStats.nodes = 1;
8749                 programStats.time = 1;
8750                 programStats.got_only_move = 1;
8751
8752                 /* Not really, but we also use this member to
8753                    mean "line isn't going to change" (Crafty
8754                    isn't searching, so stats won't change) */
8755                 programStats.line_is_book = 1;
8756
8757                 SendProgramStatsToFrontend( cps, &programStats );
8758
8759                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8760                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8761                     DisplayMove(currentMove - 1);
8762                 }
8763                 return;
8764             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8765                               &time, &nodes, &plylev, &mvleft,
8766                               &mvtot, mvname) >= 5) {
8767                 /* The stat01: line is from Crafty (9.29+) in response
8768                    to the "." command */
8769                 programStats.seen_stat = 1;
8770                 cps->maybeThinking = TRUE;
8771
8772                 if (programStats.got_only_move || !appData.periodicUpdates)
8773                   return;
8774
8775                 programStats.depth = plylev;
8776                 programStats.time = time;
8777                 programStats.nodes = nodes;
8778                 programStats.moves_left = mvleft;
8779                 programStats.nr_moves = mvtot;
8780                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8781                 programStats.ok_to_send = 1;
8782                 programStats.movelist[0] = '\0';
8783
8784                 SendProgramStatsToFrontend( cps, &programStats );
8785
8786                 return;
8787
8788             } else if (strncmp(message,"++",2) == 0) {
8789                 /* Crafty 9.29+ outputs this */
8790                 programStats.got_fail = 2;
8791                 return;
8792
8793             } else if (strncmp(message,"--",2) == 0) {
8794                 /* Crafty 9.29+ outputs this */
8795                 programStats.got_fail = 1;
8796                 return;
8797
8798             } else if (thinkOutput[0] != NULLCHAR &&
8799                        strncmp(message, "    ", 4) == 0) {
8800                 unsigned message_len;
8801
8802                 p = message;
8803                 while (*p && *p == ' ') p++;
8804
8805                 message_len = strlen( p );
8806
8807                 /* [AS] Avoid buffer overflow */
8808                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8809                     strcat(thinkOutput, " ");
8810                     strcat(thinkOutput, p);
8811                 }
8812
8813                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8814                     strcat(programStats.movelist, " ");
8815                     strcat(programStats.movelist, p);
8816                 }
8817
8818                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8819                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8820                     DisplayMove(currentMove - 1);
8821                 }
8822                 return;
8823             }
8824         }
8825         else {
8826             buf1[0] = NULLCHAR;
8827
8828             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8829                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8830             {
8831                 ChessProgramStats cpstats;
8832
8833                 if (plyext != ' ' && plyext != '\t') {
8834                     time *= 100;
8835                 }
8836
8837                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8838                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8839                     curscore = -curscore;
8840                 }
8841
8842                 cpstats.depth = plylev;
8843                 cpstats.nodes = nodes;
8844                 cpstats.time = time;
8845                 cpstats.score = curscore;
8846                 cpstats.got_only_move = 0;
8847                 cpstats.movelist[0] = '\0';
8848
8849                 if (buf1[0] != NULLCHAR) {
8850                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8851                 }
8852
8853                 cpstats.ok_to_send = 0;
8854                 cpstats.line_is_book = 0;
8855                 cpstats.nr_moves = 0;
8856                 cpstats.moves_left = 0;
8857
8858                 SendProgramStatsToFrontend( cps, &cpstats );
8859             }
8860         }
8861     }
8862 }
8863
8864
8865 /* Parse a game score from the character string "game", and
8866    record it as the history of the current game.  The game
8867    score is NOT assumed to start from the standard position.
8868    The display is not updated in any way.
8869    */
8870 void
8871 ParseGameHistory(game)
8872      char *game;
8873 {
8874     ChessMove moveType;
8875     int fromX, fromY, toX, toY, boardIndex;
8876     char promoChar;
8877     char *p, *q;
8878     char buf[MSG_SIZ];
8879
8880     if (appData.debugMode)
8881       fprintf(debugFP, "Parsing game history: %s\n", game);
8882
8883     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8884     gameInfo.site = StrSave(appData.icsHost);
8885     gameInfo.date = PGNDate();
8886     gameInfo.round = StrSave("-");
8887
8888     /* Parse out names of players */
8889     while (*game == ' ') game++;
8890     p = buf;
8891     while (*game != ' ') *p++ = *game++;
8892     *p = NULLCHAR;
8893     gameInfo.white = StrSave(buf);
8894     while (*game == ' ') game++;
8895     p = buf;
8896     while (*game != ' ' && *game != '\n') *p++ = *game++;
8897     *p = NULLCHAR;
8898     gameInfo.black = StrSave(buf);
8899
8900     /* Parse moves */
8901     boardIndex = blackPlaysFirst ? 1 : 0;
8902     yynewstr(game);
8903     for (;;) {
8904         yyboardindex = boardIndex;
8905         moveType = (ChessMove) Myylex();
8906         switch (moveType) {
8907           case IllegalMove:             /* maybe suicide chess, etc. */
8908   if (appData.debugMode) {
8909     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8910     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8911     setbuf(debugFP, NULL);
8912   }
8913           case WhitePromotion:
8914           case BlackPromotion:
8915           case WhiteNonPromotion:
8916           case BlackNonPromotion:
8917           case NormalMove:
8918           case WhiteCapturesEnPassant:
8919           case BlackCapturesEnPassant:
8920           case WhiteKingSideCastle:
8921           case WhiteQueenSideCastle:
8922           case BlackKingSideCastle:
8923           case BlackQueenSideCastle:
8924           case WhiteKingSideCastleWild:
8925           case WhiteQueenSideCastleWild:
8926           case BlackKingSideCastleWild:
8927           case BlackQueenSideCastleWild:
8928           /* PUSH Fabien */
8929           case WhiteHSideCastleFR:
8930           case WhiteASideCastleFR:
8931           case BlackHSideCastleFR:
8932           case BlackASideCastleFR:
8933           /* POP Fabien */
8934             fromX = currentMoveString[0] - AAA;
8935             fromY = currentMoveString[1] - ONE;
8936             toX = currentMoveString[2] - AAA;
8937             toY = currentMoveString[3] - ONE;
8938             promoChar = currentMoveString[4];
8939             break;
8940           case WhiteDrop:
8941           case BlackDrop:
8942             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8943             fromX = moveType == WhiteDrop ?
8944               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8945             (int) CharToPiece(ToLower(currentMoveString[0]));
8946             fromY = DROP_RANK;
8947             toX = currentMoveString[2] - AAA;
8948             toY = currentMoveString[3] - ONE;
8949             promoChar = NULLCHAR;
8950             break;
8951           case AmbiguousMove:
8952             /* bug? */
8953             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8954   if (appData.debugMode) {
8955     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8956     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8957     setbuf(debugFP, NULL);
8958   }
8959             DisplayError(buf, 0);
8960             return;
8961           case ImpossibleMove:
8962             /* bug? */
8963             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8964   if (appData.debugMode) {
8965     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8966     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8967     setbuf(debugFP, NULL);
8968   }
8969             DisplayError(buf, 0);
8970             return;
8971           case EndOfFile:
8972             if (boardIndex < backwardMostMove) {
8973                 /* Oops, gap.  How did that happen? */
8974                 DisplayError(_("Gap in move list"), 0);
8975                 return;
8976             }
8977             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8978             if (boardIndex > forwardMostMove) {
8979                 forwardMostMove = boardIndex;
8980             }
8981             return;
8982           case ElapsedTime:
8983             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8984                 strcat(parseList[boardIndex-1], " ");
8985                 strcat(parseList[boardIndex-1], yy_text);
8986             }
8987             continue;
8988           case Comment:
8989           case PGNTag:
8990           case NAG:
8991           default:
8992             /* ignore */
8993             continue;
8994           case WhiteWins:
8995           case BlackWins:
8996           case GameIsDrawn:
8997           case GameUnfinished:
8998             if (gameMode == IcsExamining) {
8999                 if (boardIndex < backwardMostMove) {
9000                     /* Oops, gap.  How did that happen? */
9001                     return;
9002                 }
9003                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9004                 return;
9005             }
9006             gameInfo.result = moveType;
9007             p = strchr(yy_text, '{');
9008             if (p == NULL) p = strchr(yy_text, '(');
9009             if (p == NULL) {
9010                 p = yy_text;
9011                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9012             } else {
9013                 q = strchr(p, *p == '{' ? '}' : ')');
9014                 if (q != NULL) *q = NULLCHAR;
9015                 p++;
9016             }
9017             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9018             gameInfo.resultDetails = StrSave(p);
9019             continue;
9020         }
9021         if (boardIndex >= forwardMostMove &&
9022             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9023             backwardMostMove = blackPlaysFirst ? 1 : 0;
9024             return;
9025         }
9026         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9027                                  fromY, fromX, toY, toX, promoChar,
9028                                  parseList[boardIndex]);
9029         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9030         /* currentMoveString is set as a side-effect of yylex */
9031         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9032         strcat(moveList[boardIndex], "\n");
9033         boardIndex++;
9034         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9035         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9036           case MT_NONE:
9037           case MT_STALEMATE:
9038           default:
9039             break;
9040           case MT_CHECK:
9041             if(gameInfo.variant != VariantShogi)
9042                 strcat(parseList[boardIndex - 1], "+");
9043             break;
9044           case MT_CHECKMATE:
9045           case MT_STAINMATE:
9046             strcat(parseList[boardIndex - 1], "#");
9047             break;
9048         }
9049     }
9050 }
9051
9052
9053 /* Apply a move to the given board  */
9054 void
9055 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9056      int fromX, fromY, toX, toY;
9057      int promoChar;
9058      Board board;
9059 {
9060   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9061   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9062
9063     /* [HGM] compute & store e.p. status and castling rights for new position */
9064     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9065
9066       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9067       oldEP = (signed char)board[EP_STATUS];
9068       board[EP_STATUS] = EP_NONE;
9069
9070   if (fromY == DROP_RANK) {
9071         /* must be first */
9072         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9073             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9074             return;
9075         }
9076         piece = board[toY][toX] = (ChessSquare) fromX;
9077   } else {
9078       int i;
9079
9080       if( board[toY][toX] != EmptySquare )
9081            board[EP_STATUS] = EP_CAPTURE;
9082
9083       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9084            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9085                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9086       } else
9087       if( board[fromY][fromX] == WhitePawn ) {
9088            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9089                board[EP_STATUS] = EP_PAWN_MOVE;
9090            if( toY-fromY==2) {
9091                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9092                         gameInfo.variant != VariantBerolina || toX < fromX)
9093                       board[EP_STATUS] = toX | berolina;
9094                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9095                         gameInfo.variant != VariantBerolina || toX > fromX)
9096                       board[EP_STATUS] = toX;
9097            }
9098       } else
9099       if( board[fromY][fromX] == BlackPawn ) {
9100            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9101                board[EP_STATUS] = EP_PAWN_MOVE;
9102            if( toY-fromY== -2) {
9103                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9104                         gameInfo.variant != VariantBerolina || toX < fromX)
9105                       board[EP_STATUS] = toX | berolina;
9106                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9107                         gameInfo.variant != VariantBerolina || toX > fromX)
9108                       board[EP_STATUS] = toX;
9109            }
9110        }
9111
9112        for(i=0; i<nrCastlingRights; i++) {
9113            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9114               board[CASTLING][i] == toX   && castlingRank[i] == toY
9115              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9116        }
9117
9118      if (fromX == toX && fromY == toY) return;
9119
9120      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9121      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9122      if(gameInfo.variant == VariantKnightmate)
9123          king += (int) WhiteUnicorn - (int) WhiteKing;
9124
9125     /* Code added by Tord: */
9126     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9127     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9128         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9129       board[fromY][fromX] = EmptySquare;
9130       board[toY][toX] = EmptySquare;
9131       if((toX > fromX) != (piece == WhiteRook)) {
9132         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9133       } else {
9134         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9135       }
9136     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9137                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9138       board[fromY][fromX] = EmptySquare;
9139       board[toY][toX] = EmptySquare;
9140       if((toX > fromX) != (piece == BlackRook)) {
9141         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9142       } else {
9143         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9144       }
9145     /* End of code added by Tord */
9146
9147     } else if (board[fromY][fromX] == king
9148         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9149         && toY == fromY && toX > fromX+1) {
9150         board[fromY][fromX] = EmptySquare;
9151         board[toY][toX] = king;
9152         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9153         board[fromY][BOARD_RGHT-1] = EmptySquare;
9154     } else if (board[fromY][fromX] == king
9155         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9156                && toY == fromY && toX < fromX-1) {
9157         board[fromY][fromX] = EmptySquare;
9158         board[toY][toX] = king;
9159         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9160         board[fromY][BOARD_LEFT] = EmptySquare;
9161     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9162                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9163                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9164                ) {
9165         /* white pawn promotion */
9166         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9167         if(gameInfo.variant==VariantBughouse ||
9168            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9169             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9170         board[fromY][fromX] = EmptySquare;
9171     } else if ((fromY >= BOARD_HEIGHT>>1)
9172                && (toX != fromX)
9173                && gameInfo.variant != VariantXiangqi
9174                && gameInfo.variant != VariantBerolina
9175                && (board[fromY][fromX] == WhitePawn)
9176                && (board[toY][toX] == EmptySquare)) {
9177         board[fromY][fromX] = EmptySquare;
9178         board[toY][toX] = WhitePawn;
9179         captured = board[toY - 1][toX];
9180         board[toY - 1][toX] = EmptySquare;
9181     } else if ((fromY == BOARD_HEIGHT-4)
9182                && (toX == fromX)
9183                && gameInfo.variant == VariantBerolina
9184                && (board[fromY][fromX] == WhitePawn)
9185                && (board[toY][toX] == EmptySquare)) {
9186         board[fromY][fromX] = EmptySquare;
9187         board[toY][toX] = WhitePawn;
9188         if(oldEP & EP_BEROLIN_A) {
9189                 captured = board[fromY][fromX-1];
9190                 board[fromY][fromX-1] = EmptySquare;
9191         }else{  captured = board[fromY][fromX+1];
9192                 board[fromY][fromX+1] = EmptySquare;
9193         }
9194     } else if (board[fromY][fromX] == king
9195         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9196                && toY == fromY && toX > fromX+1) {
9197         board[fromY][fromX] = EmptySquare;
9198         board[toY][toX] = king;
9199         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9200         board[fromY][BOARD_RGHT-1] = EmptySquare;
9201     } else if (board[fromY][fromX] == king
9202         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9203                && toY == fromY && toX < fromX-1) {
9204         board[fromY][fromX] = EmptySquare;
9205         board[toY][toX] = king;
9206         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9207         board[fromY][BOARD_LEFT] = EmptySquare;
9208     } else if (fromY == 7 && fromX == 3
9209                && board[fromY][fromX] == BlackKing
9210                && toY == 7 && toX == 5) {
9211         board[fromY][fromX] = EmptySquare;
9212         board[toY][toX] = BlackKing;
9213         board[fromY][7] = EmptySquare;
9214         board[toY][4] = BlackRook;
9215     } else if (fromY == 7 && fromX == 3
9216                && board[fromY][fromX] == BlackKing
9217                && toY == 7 && toX == 1) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = BlackKing;
9220         board[fromY][0] = EmptySquare;
9221         board[toY][2] = BlackRook;
9222     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9223                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9224                && toY < promoRank && promoChar
9225                ) {
9226         /* black pawn promotion */
9227         board[toY][toX] = CharToPiece(ToLower(promoChar));
9228         if(gameInfo.variant==VariantBughouse ||
9229            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9230             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9231         board[fromY][fromX] = EmptySquare;
9232     } else if ((fromY < BOARD_HEIGHT>>1)
9233                && (toX != fromX)
9234                && gameInfo.variant != VariantXiangqi
9235                && gameInfo.variant != VariantBerolina
9236                && (board[fromY][fromX] == BlackPawn)
9237                && (board[toY][toX] == EmptySquare)) {
9238         board[fromY][fromX] = EmptySquare;
9239         board[toY][toX] = BlackPawn;
9240         captured = board[toY + 1][toX];
9241         board[toY + 1][toX] = EmptySquare;
9242     } else if ((fromY == 3)
9243                && (toX == fromX)
9244                && gameInfo.variant == VariantBerolina
9245                && (board[fromY][fromX] == BlackPawn)
9246                && (board[toY][toX] == EmptySquare)) {
9247         board[fromY][fromX] = EmptySquare;
9248         board[toY][toX] = BlackPawn;
9249         if(oldEP & EP_BEROLIN_A) {
9250                 captured = board[fromY][fromX-1];
9251                 board[fromY][fromX-1] = EmptySquare;
9252         }else{  captured = board[fromY][fromX+1];
9253                 board[fromY][fromX+1] = EmptySquare;
9254         }
9255     } else {
9256         board[toY][toX] = board[fromY][fromX];
9257         board[fromY][fromX] = EmptySquare;
9258     }
9259   }
9260
9261     if (gameInfo.holdingsWidth != 0) {
9262
9263       /* !!A lot more code needs to be written to support holdings  */
9264       /* [HGM] OK, so I have written it. Holdings are stored in the */
9265       /* penultimate board files, so they are automaticlly stored   */
9266       /* in the game history.                                       */
9267       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9268                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9269         /* Delete from holdings, by decreasing count */
9270         /* and erasing image if necessary            */
9271         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9272         if(p < (int) BlackPawn) { /* white drop */
9273              p -= (int)WhitePawn;
9274                  p = PieceToNumber((ChessSquare)p);
9275              if(p >= gameInfo.holdingsSize) p = 0;
9276              if(--board[p][BOARD_WIDTH-2] <= 0)
9277                   board[p][BOARD_WIDTH-1] = EmptySquare;
9278              if((int)board[p][BOARD_WIDTH-2] < 0)
9279                         board[p][BOARD_WIDTH-2] = 0;
9280         } else {                  /* black drop */
9281              p -= (int)BlackPawn;
9282                  p = PieceToNumber((ChessSquare)p);
9283              if(p >= gameInfo.holdingsSize) p = 0;
9284              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9285                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9286              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9287                         board[BOARD_HEIGHT-1-p][1] = 0;
9288         }
9289       }
9290       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9291           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9292         /* [HGM] holdings: Add to holdings, if holdings exist */
9293         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9294                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9295                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9296         }
9297         p = (int) captured;
9298         if (p >= (int) BlackPawn) {
9299           p -= (int)BlackPawn;
9300           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9301                   /* in Shogi restore piece to its original  first */
9302                   captured = (ChessSquare) (DEMOTED captured);
9303                   p = DEMOTED p;
9304           }
9305           p = PieceToNumber((ChessSquare)p);
9306           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9307           board[p][BOARD_WIDTH-2]++;
9308           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9309         } else {
9310           p -= (int)WhitePawn;
9311           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9312                   captured = (ChessSquare) (DEMOTED captured);
9313                   p = DEMOTED p;
9314           }
9315           p = PieceToNumber((ChessSquare)p);
9316           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9317           board[BOARD_HEIGHT-1-p][1]++;
9318           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9319         }
9320       }
9321     } else if (gameInfo.variant == VariantAtomic) {
9322       if (captured != EmptySquare) {
9323         int y, x;
9324         for (y = toY-1; y <= toY+1; y++) {
9325           for (x = toX-1; x <= toX+1; x++) {
9326             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9327                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9328               board[y][x] = EmptySquare;
9329             }
9330           }
9331         }
9332         board[toY][toX] = EmptySquare;
9333       }
9334     }
9335     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9336         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9337     } else
9338     if(promoChar == '+') {
9339         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9340         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9341     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9342         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9343     }
9344     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9345                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9346         // [HGM] superchess: take promotion piece out of holdings
9347         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9348         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9349             if(!--board[k][BOARD_WIDTH-2])
9350                 board[k][BOARD_WIDTH-1] = EmptySquare;
9351         } else {
9352             if(!--board[BOARD_HEIGHT-1-k][1])
9353                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9354         }
9355     }
9356
9357 }
9358
9359 /* Updates forwardMostMove */
9360 void
9361 MakeMove(fromX, fromY, toX, toY, promoChar)
9362      int fromX, fromY, toX, toY;
9363      int promoChar;
9364 {
9365 //    forwardMostMove++; // [HGM] bare: moved downstream
9366
9367     (void) CoordsToAlgebraic(boards[forwardMostMove],
9368                              PosFlags(forwardMostMove),
9369                              fromY, fromX, toY, toX, promoChar,
9370                              parseList[forwardMostMove]);
9371
9372     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9373         int timeLeft; static int lastLoadFlag=0; int king, piece;
9374         piece = boards[forwardMostMove][fromY][fromX];
9375         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9376         if(gameInfo.variant == VariantKnightmate)
9377             king += (int) WhiteUnicorn - (int) WhiteKing;
9378         if(forwardMostMove == 0) {
9379             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9380                 fprintf(serverMoves, "%s;", UserName());
9381             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9382                 fprintf(serverMoves, "%s;", second.tidy);
9383             fprintf(serverMoves, "%s;", first.tidy);
9384             if(gameMode == MachinePlaysWhite)
9385                 fprintf(serverMoves, "%s;", UserName());
9386             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9387                 fprintf(serverMoves, "%s;", second.tidy);
9388         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9389         lastLoadFlag = loadFlag;
9390         // print base move
9391         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9392         // print castling suffix
9393         if( toY == fromY && piece == king ) {
9394             if(toX-fromX > 1)
9395                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9396             if(fromX-toX >1)
9397                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9398         }
9399         // e.p. suffix
9400         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9401              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9402              boards[forwardMostMove][toY][toX] == EmptySquare
9403              && fromX != toX && fromY != toY)
9404                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9405         // promotion suffix
9406         if(promoChar != NULLCHAR)
9407                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9408         if(!loadFlag) {
9409                 char buf[MOVE_LEN*2], *p; int len;
9410             fprintf(serverMoves, "/%d/%d",
9411                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9412             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9413             else                      timeLeft = blackTimeRemaining/1000;
9414             fprintf(serverMoves, "/%d", timeLeft);
9415                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9416                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9417                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9418             fprintf(serverMoves, "/%s", buf);
9419         }
9420         fflush(serverMoves);
9421     }
9422
9423     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9424       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9425                         0, 1);
9426       return;
9427     }
9428     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9429     if (commentList[forwardMostMove+1] != NULL) {
9430         free(commentList[forwardMostMove+1]);
9431         commentList[forwardMostMove+1] = NULL;
9432     }
9433     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9434     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9435     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9436     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9437     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9438     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9439     gameInfo.result = GameUnfinished;
9440     if (gameInfo.resultDetails != NULL) {
9441         free(gameInfo.resultDetails);
9442         gameInfo.resultDetails = NULL;
9443     }
9444     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9445                               moveList[forwardMostMove - 1]);
9446     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9447       case MT_NONE:
9448       case MT_STALEMATE:
9449       default:
9450         break;
9451       case MT_CHECK:
9452         if(gameInfo.variant != VariantShogi)
9453             strcat(parseList[forwardMostMove - 1], "+");
9454         break;
9455       case MT_CHECKMATE:
9456       case MT_STAINMATE:
9457         strcat(parseList[forwardMostMove - 1], "#");
9458         break;
9459     }
9460     if (appData.debugMode) {
9461         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9462     }
9463
9464 }
9465
9466 /* Updates currentMove if not pausing */
9467 void
9468 ShowMove(fromX, fromY, toX, toY)
9469 {
9470     int instant = (gameMode == PlayFromGameFile) ?
9471         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9472     if(appData.noGUI) return;
9473     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9474         if (!instant) {
9475             if (forwardMostMove == currentMove + 1) {
9476                 AnimateMove(boards[forwardMostMove - 1],
9477                             fromX, fromY, toX, toY);
9478             }
9479             if (appData.highlightLastMove) {
9480                 SetHighlights(fromX, fromY, toX, toY);
9481             }
9482         }
9483         currentMove = forwardMostMove;
9484     }
9485
9486     if (instant) return;
9487
9488     DisplayMove(currentMove - 1);
9489     DrawPosition(FALSE, boards[currentMove]);
9490     DisplayBothClocks();
9491     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9492     DisplayBook(currentMove);
9493 }
9494
9495 void SendEgtPath(ChessProgramState *cps)
9496 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9497         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9498
9499         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9500
9501         while(*p) {
9502             char c, *q = name+1, *r, *s;
9503
9504             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9505             while(*p && *p != ',') *q++ = *p++;
9506             *q++ = ':'; *q = 0;
9507             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9508                 strcmp(name, ",nalimov:") == 0 ) {
9509                 // take nalimov path from the menu-changeable option first, if it is defined
9510               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9511                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9512             } else
9513             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9514                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9515                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9516                 s = r = StrStr(s, ":") + 1; // beginning of path info
9517                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9518                 c = *r; *r = 0;             // temporarily null-terminate path info
9519                     *--q = 0;               // strip of trailig ':' from name
9520                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9521                 *r = c;
9522                 SendToProgram(buf,cps);     // send egtbpath command for this format
9523             }
9524             if(*p == ',') p++; // read away comma to position for next format name
9525         }
9526 }
9527
9528 void
9529 InitChessProgram(cps, setup)
9530      ChessProgramState *cps;
9531      int setup; /* [HGM] needed to setup FRC opening position */
9532 {
9533     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9534     if (appData.noChessProgram) return;
9535     hintRequested = FALSE;
9536     bookRequested = FALSE;
9537
9538     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9539     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9540     if(cps->memSize) { /* [HGM] memory */
9541       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9542         SendToProgram(buf, cps);
9543     }
9544     SendEgtPath(cps); /* [HGM] EGT */
9545     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9546       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9547         SendToProgram(buf, cps);
9548     }
9549
9550     SendToProgram(cps->initString, cps);
9551     if (gameInfo.variant != VariantNormal &&
9552         gameInfo.variant != VariantLoadable
9553         /* [HGM] also send variant if board size non-standard */
9554         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9555                                             ) {
9556       char *v = VariantName(gameInfo.variant);
9557       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9558         /* [HGM] in protocol 1 we have to assume all variants valid */
9559         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9560         DisplayFatalError(buf, 0, 1);
9561         return;
9562       }
9563
9564       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9565       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9566       if( gameInfo.variant == VariantXiangqi )
9567            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9568       if( gameInfo.variant == VariantShogi )
9569            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9570       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9571            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9572       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9573           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9574            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9575       if( gameInfo.variant == VariantCourier )
9576            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9577       if( gameInfo.variant == VariantSuper )
9578            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9579       if( gameInfo.variant == VariantGreat )
9580            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9581       if( gameInfo.variant == VariantSChess )
9582            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9583       if( gameInfo.variant == VariantGrand )
9584            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9585
9586       if(overruled) {
9587         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9588                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9589            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9590            if(StrStr(cps->variants, b) == NULL) {
9591                // specific sized variant not known, check if general sizing allowed
9592                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9593                    if(StrStr(cps->variants, "boardsize") == NULL) {
9594                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9595                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9596                        DisplayFatalError(buf, 0, 1);
9597                        return;
9598                    }
9599                    /* [HGM] here we really should compare with the maximum supported board size */
9600                }
9601            }
9602       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9603       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9604       SendToProgram(buf, cps);
9605     }
9606     currentlyInitializedVariant = gameInfo.variant;
9607
9608     /* [HGM] send opening position in FRC to first engine */
9609     if(setup) {
9610           SendToProgram("force\n", cps);
9611           SendBoard(cps, 0);
9612           /* engine is now in force mode! Set flag to wake it up after first move. */
9613           setboardSpoiledMachineBlack = 1;
9614     }
9615
9616     if (cps->sendICS) {
9617       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9618       SendToProgram(buf, cps);
9619     }
9620     cps->maybeThinking = FALSE;
9621     cps->offeredDraw = 0;
9622     if (!appData.icsActive) {
9623         SendTimeControl(cps, movesPerSession, timeControl,
9624                         timeIncrement, appData.searchDepth,
9625                         searchTime);
9626     }
9627     if (appData.showThinking
9628         // [HGM] thinking: four options require thinking output to be sent
9629         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9630                                 ) {
9631         SendToProgram("post\n", cps);
9632     }
9633     SendToProgram("hard\n", cps);
9634     if (!appData.ponderNextMove) {
9635         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9636            it without being sure what state we are in first.  "hard"
9637            is not a toggle, so that one is OK.
9638          */
9639         SendToProgram("easy\n", cps);
9640     }
9641     if (cps->usePing) {
9642       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9643       SendToProgram(buf, cps);
9644     }
9645     cps->initDone = TRUE;
9646     ClearEngineOutputPane(cps == &second);
9647 }
9648
9649
9650 void
9651 StartChessProgram(cps)
9652      ChessProgramState *cps;
9653 {
9654     char buf[MSG_SIZ];
9655     int err;
9656
9657     if (appData.noChessProgram) return;
9658     cps->initDone = FALSE;
9659
9660     if (strcmp(cps->host, "localhost") == 0) {
9661         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9662     } else if (*appData.remoteShell == NULLCHAR) {
9663         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9664     } else {
9665         if (*appData.remoteUser == NULLCHAR) {
9666           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9667                     cps->program);
9668         } else {
9669           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9670                     cps->host, appData.remoteUser, cps->program);
9671         }
9672         err = StartChildProcess(buf, "", &cps->pr);
9673     }
9674
9675     if (err != 0) {
9676       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9677         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9678         if(cps != &first) return;
9679         appData.noChessProgram = TRUE;
9680         ThawUI();
9681         SetNCPMode();
9682 //      DisplayFatalError(buf, err, 1);
9683 //      cps->pr = NoProc;
9684 //      cps->isr = NULL;
9685         return;
9686     }
9687
9688     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9689     if (cps->protocolVersion > 1) {
9690       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9691       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9692       cps->comboCnt = 0;  //                and values of combo boxes
9693       SendToProgram(buf, cps);
9694     } else {
9695       SendToProgram("xboard\n", cps);
9696     }
9697 }
9698
9699 void
9700 TwoMachinesEventIfReady P((void))
9701 {
9702   static int curMess = 0;
9703   if (first.lastPing != first.lastPong) {
9704     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9705     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9706     return;
9707   }
9708   if (second.lastPing != second.lastPong) {
9709     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9710     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9711     return;
9712   }
9713   DisplayMessage("", ""); curMess = 0;
9714   ThawUI();
9715   TwoMachinesEvent();
9716 }
9717
9718 char *
9719 MakeName(char *template)
9720 {
9721     time_t clock;
9722     struct tm *tm;
9723     static char buf[MSG_SIZ];
9724     char *p = buf;
9725     int i;
9726
9727     clock = time((time_t *)NULL);
9728     tm = localtime(&clock);
9729
9730     while(*p++ = *template++) if(p[-1] == '%') {
9731         switch(*template++) {
9732           case 0:   *p = 0; return buf;
9733           case 'Y': i = tm->tm_year+1900; break;
9734           case 'y': i = tm->tm_year-100; break;
9735           case 'M': i = tm->tm_mon+1; break;
9736           case 'd': i = tm->tm_mday; break;
9737           case 'h': i = tm->tm_hour; break;
9738           case 'm': i = tm->tm_min; break;
9739           case 's': i = tm->tm_sec; break;
9740           default:  i = 0;
9741         }
9742         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9743     }
9744     return buf;
9745 }
9746
9747 int
9748 CountPlayers(char *p)
9749 {
9750     int n = 0;
9751     while(p = strchr(p, '\n')) p++, n++; // count participants
9752     return n;
9753 }
9754
9755 FILE *
9756 WriteTourneyFile(char *results, FILE *f)
9757 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9758     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9759     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9760         // create a file with tournament description
9761         fprintf(f, "-participants {%s}\n", appData.participants);
9762         fprintf(f, "-seedBase %d\n", appData.seedBase);
9763         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9764         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9765         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9766         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9767         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9768         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9769         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9770         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9771         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9772         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9773         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9774         if(searchTime > 0)
9775                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9776         else {
9777                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9778                 fprintf(f, "-tc %s\n", appData.timeControl);
9779                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9780         }
9781         fprintf(f, "-results \"%s\"\n", results);
9782     }
9783     return f;
9784 }
9785
9786 #define MAXENGINES 1000
9787 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9788
9789 void Substitute(char *participants, int expunge)
9790 {
9791     int i, changed, changes=0, nPlayers=0;
9792     char *p, *q, *r, buf[MSG_SIZ];
9793     if(participants == NULL) return;
9794     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9795     r = p = participants; q = appData.participants;
9796     while(*p && *p == *q) {
9797         if(*p == '\n') r = p+1, nPlayers++;
9798         p++; q++;
9799     }
9800     if(*p) { // difference
9801         while(*p && *p++ != '\n');
9802         while(*q && *q++ != '\n');
9803       changed = nPlayers;
9804         changes = 1 + (strcmp(p, q) != 0);
9805     }
9806     if(changes == 1) { // a single engine mnemonic was changed
9807         q = r; while(*q) nPlayers += (*q++ == '\n');
9808         p = buf; while(*r && (*p = *r++) != '\n') p++;
9809         *p = NULLCHAR;
9810         NamesToList(firstChessProgramNames, command, mnemonic);
9811         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9812         if(mnemonic[i]) { // The substitute is valid
9813             FILE *f;
9814             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9815                 flock(fileno(f), LOCK_EX);
9816                 ParseArgsFromFile(f);
9817                 fseek(f, 0, SEEK_SET);
9818                 FREE(appData.participants); appData.participants = participants;
9819                 if(expunge) { // erase results of replaced engine
9820                     int len = strlen(appData.results), w, b, dummy;
9821                     for(i=0; i<len; i++) {
9822                         Pairing(i, nPlayers, &w, &b, &dummy);
9823                         if((w == changed || b == changed) && appData.results[i] == '*') {
9824                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9825                             fclose(f);
9826                             return;
9827                         }
9828                     }
9829                     for(i=0; i<len; i++) {
9830                         Pairing(i, nPlayers, &w, &b, &dummy);
9831                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9832                     }
9833                 }
9834                 WriteTourneyFile(appData.results, f);
9835                 fclose(f); // release lock
9836                 return;
9837             }
9838         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9839     }
9840     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9841     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9842     free(participants);
9843     return;
9844 }
9845
9846 int
9847 CreateTourney(char *name)
9848 {
9849         FILE *f;
9850         if(matchMode && strcmp(name, appData.tourneyFile)) {
9851              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9852         }
9853         if(name[0] == NULLCHAR) {
9854             if(appData.participants[0])
9855                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9856             return 0;
9857         }
9858         f = fopen(name, "r");
9859         if(f) { // file exists
9860             ASSIGN(appData.tourneyFile, name);
9861             ParseArgsFromFile(f); // parse it
9862         } else {
9863             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9864             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9865                 DisplayError(_("Not enough participants"), 0);
9866                 return 0;
9867             }
9868             ASSIGN(appData.tourneyFile, name);
9869             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9870             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9871         }
9872         fclose(f);
9873         appData.noChessProgram = FALSE;
9874         appData.clockMode = TRUE;
9875         SetGNUMode();
9876         return 1;
9877 }
9878
9879 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9880 {
9881     char buf[MSG_SIZ], *p, *q;
9882     int i=1;
9883     while(*names) {
9884         p = names; q = buf;
9885         while(*p && *p != '\n') *q++ = *p++;
9886         *q = 0;
9887         if(engineList[i]) free(engineList[i]);
9888         engineList[i] = strdup(buf);
9889         if(*p == '\n') p++;
9890         TidyProgramName(engineList[i], "localhost", buf);
9891         if(engineMnemonic[i]) free(engineMnemonic[i]);
9892         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9893             strcat(buf, " (");
9894             sscanf(q + 8, "%s", buf + strlen(buf));
9895             strcat(buf, ")");
9896         }
9897         engineMnemonic[i] = strdup(buf);
9898         names = p; i++;
9899       if(i > MAXENGINES - 2) break;
9900     }
9901     engineList[i] = engineMnemonic[i] = NULL;
9902 }
9903
9904 // following implemented as macro to avoid type limitations
9905 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9906
9907 void SwapEngines(int n)
9908 {   // swap settings for first engine and other engine (so far only some selected options)
9909     int h;
9910     char *p;
9911     if(n == 0) return;
9912     SWAP(directory, p)
9913     SWAP(chessProgram, p)
9914     SWAP(isUCI, h)
9915     SWAP(hasOwnBookUCI, h)
9916     SWAP(protocolVersion, h)
9917     SWAP(reuse, h)
9918     SWAP(scoreIsAbsolute, h)
9919     SWAP(timeOdds, h)
9920     SWAP(logo, p)
9921     SWAP(pgnName, p)
9922     SWAP(pvSAN, h)
9923 }
9924
9925 void
9926 SetPlayer(int player)
9927 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9928     int i;
9929     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9930     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9931     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9932     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9933     if(mnemonic[i]) {
9934         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9935         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9936         ParseArgsFromString(buf);
9937     }
9938     free(engineName);
9939 }
9940
9941 int
9942 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9943 {   // determine players from game number
9944     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9945
9946     if(appData.tourneyType == 0) {
9947         roundsPerCycle = (nPlayers - 1) | 1;
9948         pairingsPerRound = nPlayers / 2;
9949     } else if(appData.tourneyType > 0) {
9950         roundsPerCycle = nPlayers - appData.tourneyType;
9951         pairingsPerRound = appData.tourneyType;
9952     }
9953     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9954     gamesPerCycle = gamesPerRound * roundsPerCycle;
9955     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9956     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9957     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9958     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9959     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9960     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9961
9962     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9963     if(appData.roundSync) *syncInterval = gamesPerRound;
9964
9965     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9966
9967     if(appData.tourneyType == 0) {
9968         if(curPairing == (nPlayers-1)/2 ) {
9969             *whitePlayer = curRound;
9970             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9971         } else {
9972             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9973             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9974             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9975             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9976         }
9977     } else if(appData.tourneyType > 0) {
9978         *whitePlayer = curPairing;
9979         *blackPlayer = curRound + appData.tourneyType;
9980     }
9981
9982     // take care of white/black alternation per round. 
9983     // For cycles and games this is already taken care of by default, derived from matchGame!
9984     return curRound & 1;
9985 }
9986
9987 int
9988 NextTourneyGame(int nr, int *swapColors)
9989 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9990     char *p, *q;
9991     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9992     FILE *tf;
9993     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9994     tf = fopen(appData.tourneyFile, "r");
9995     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9996     ParseArgsFromFile(tf); fclose(tf);
9997     InitTimeControls(); // TC might be altered from tourney file
9998
9999     nPlayers = CountPlayers(appData.participants); // count participants
10000     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10001     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10002
10003     if(syncInterval) {
10004         p = q = appData.results;
10005         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10006         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10007             DisplayMessage(_("Waiting for other game(s)"),"");
10008             waitingForGame = TRUE;
10009             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10010             return 0;
10011         }
10012         waitingForGame = FALSE;
10013     }
10014
10015     if(appData.tourneyType < 0) {
10016         if(nr>=0 && !pairingReceived) {
10017             char buf[1<<16];
10018             if(pairing.pr == NoProc) {
10019                 if(!appData.pairingEngine[0]) {
10020                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10021                     return 0;
10022                 }
10023                 StartChessProgram(&pairing); // starts the pairing engine
10024             }
10025             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10026             SendToProgram(buf, &pairing);
10027             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10028             SendToProgram(buf, &pairing);
10029             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10030         }
10031         pairingReceived = 0;                              // ... so we continue here 
10032         *swapColors = 0;
10033         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10034         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10035         matchGame = 1; roundNr = nr / syncInterval + 1;
10036     }
10037
10038     if(first.pr != NoProc) return 1; // engines already loaded
10039
10040     // redefine engines, engine dir, etc.
10041     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10042     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10043     SwapEngines(1);
10044     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10045     SwapEngines(1);         // and make that valid for second engine by swapping
10046     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10047     InitEngine(&second, 1);
10048     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10049     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10050     return 1;
10051 }
10052
10053 void
10054 NextMatchGame()
10055 {   // performs game initialization that does not invoke engines, and then tries to start the game
10056     int res, firstWhite, swapColors = 0;
10057     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10058     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10059     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10060     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10061     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10062     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10063     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10064     Reset(FALSE, first.pr != NoProc);
10065     res = LoadGameOrPosition(matchGame); // setup game
10066     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10067     if(!res) return; // abort when bad game/pos file
10068     TwoMachinesEvent();
10069 }
10070
10071 void UserAdjudicationEvent( int result )
10072 {
10073     ChessMove gameResult = GameIsDrawn;
10074
10075     if( result > 0 ) {
10076         gameResult = WhiteWins;
10077     }
10078     else if( result < 0 ) {
10079         gameResult = BlackWins;
10080     }
10081
10082     if( gameMode == TwoMachinesPlay ) {
10083         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10084     }
10085 }
10086
10087
10088 // [HGM] save: calculate checksum of game to make games easily identifiable
10089 int StringCheckSum(char *s)
10090 {
10091         int i = 0;
10092         if(s==NULL) return 0;
10093         while(*s) i = i*259 + *s++;
10094         return i;
10095 }
10096
10097 int GameCheckSum()
10098 {
10099         int i, sum=0;
10100         for(i=backwardMostMove; i<forwardMostMove; i++) {
10101                 sum += pvInfoList[i].depth;
10102                 sum += StringCheckSum(parseList[i]);
10103                 sum += StringCheckSum(commentList[i]);
10104                 sum *= 261;
10105         }
10106         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10107         return sum + StringCheckSum(commentList[i]);
10108 } // end of save patch
10109
10110 void
10111 GameEnds(result, resultDetails, whosays)
10112      ChessMove result;
10113      char *resultDetails;
10114      int whosays;
10115 {
10116     GameMode nextGameMode;
10117     int isIcsGame;
10118     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10119
10120     if(endingGame) return; /* [HGM] crash: forbid recursion */
10121     endingGame = 1;
10122     if(twoBoards) { // [HGM] dual: switch back to one board
10123         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10124         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10125     }
10126     if (appData.debugMode) {
10127       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10128               result, resultDetails ? resultDetails : "(null)", whosays);
10129     }
10130
10131     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10132
10133     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10134         /* If we are playing on ICS, the server decides when the
10135            game is over, but the engine can offer to draw, claim
10136            a draw, or resign.
10137          */
10138 #if ZIPPY
10139         if (appData.zippyPlay && first.initDone) {
10140             if (result == GameIsDrawn) {
10141                 /* In case draw still needs to be claimed */
10142                 SendToICS(ics_prefix);
10143                 SendToICS("draw\n");
10144             } else if (StrCaseStr(resultDetails, "resign")) {
10145                 SendToICS(ics_prefix);
10146                 SendToICS("resign\n");
10147             }
10148         }
10149 #endif
10150         endingGame = 0; /* [HGM] crash */
10151         return;
10152     }
10153
10154     /* If we're loading the game from a file, stop */
10155     if (whosays == GE_FILE) {
10156       (void) StopLoadGameTimer();
10157       gameFileFP = NULL;
10158     }
10159
10160     /* Cancel draw offers */
10161     first.offeredDraw = second.offeredDraw = 0;
10162
10163     /* If this is an ICS game, only ICS can really say it's done;
10164        if not, anyone can. */
10165     isIcsGame = (gameMode == IcsPlayingWhite ||
10166                  gameMode == IcsPlayingBlack ||
10167                  gameMode == IcsObserving    ||
10168                  gameMode == IcsExamining);
10169
10170     if (!isIcsGame || whosays == GE_ICS) {
10171         /* OK -- not an ICS game, or ICS said it was done */
10172         StopClocks();
10173         if (!isIcsGame && !appData.noChessProgram)
10174           SetUserThinkingEnables();
10175
10176         /* [HGM] if a machine claims the game end we verify this claim */
10177         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10178             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10179                 char claimer;
10180                 ChessMove trueResult = (ChessMove) -1;
10181
10182                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10183                                             first.twoMachinesColor[0] :
10184                                             second.twoMachinesColor[0] ;
10185
10186                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10187                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10188                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10189                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10190                 } else
10191                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10192                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10193                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10194                 } else
10195                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10196                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10197                 }
10198
10199                 // now verify win claims, but not in drop games, as we don't understand those yet
10200                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10201                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10202                     (result == WhiteWins && claimer == 'w' ||
10203                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10204                       if (appData.debugMode) {
10205                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10206                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10207                       }
10208                       if(result != trueResult) {
10209                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10210                               result = claimer == 'w' ? BlackWins : WhiteWins;
10211                               resultDetails = buf;
10212                       }
10213                 } else
10214                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10215                     && (forwardMostMove <= backwardMostMove ||
10216                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10217                         (claimer=='b')==(forwardMostMove&1))
10218                                                                                   ) {
10219                       /* [HGM] verify: draws that were not flagged are false claims */
10220                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10221                       result = claimer == 'w' ? BlackWins : WhiteWins;
10222                       resultDetails = buf;
10223                 }
10224                 /* (Claiming a loss is accepted no questions asked!) */
10225             }
10226             /* [HGM] bare: don't allow bare King to win */
10227             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10228                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10229                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10230                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10231                && result != GameIsDrawn)
10232             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10233                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10234                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10235                         if(p >= 0 && p <= (int)WhiteKing) k++;
10236                 }
10237                 if (appData.debugMode) {
10238                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10239                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10240                 }
10241                 if(k <= 1) {
10242                         result = GameIsDrawn;
10243                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10244                         resultDetails = buf;
10245                 }
10246             }
10247         }
10248
10249
10250         if(serverMoves != NULL && !loadFlag) { char c = '=';
10251             if(result==WhiteWins) c = '+';
10252             if(result==BlackWins) c = '-';
10253             if(resultDetails != NULL)
10254                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10255         }
10256         if (resultDetails != NULL) {
10257             gameInfo.result = result;
10258             gameInfo.resultDetails = StrSave(resultDetails);
10259
10260             /* display last move only if game was not loaded from file */
10261             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10262                 DisplayMove(currentMove - 1);
10263
10264             if (forwardMostMove != 0) {
10265                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10266                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10267                                                                 ) {
10268                     if (*appData.saveGameFile != NULLCHAR) {
10269                         SaveGameToFile(appData.saveGameFile, TRUE);
10270                     } else if (appData.autoSaveGames) {
10271                         AutoSaveGame();
10272                     }
10273                     if (*appData.savePositionFile != NULLCHAR) {
10274                         SavePositionToFile(appData.savePositionFile);
10275                     }
10276                 }
10277             }
10278
10279             /* Tell program how game ended in case it is learning */
10280             /* [HGM] Moved this to after saving the PGN, just in case */
10281             /* engine died and we got here through time loss. In that */
10282             /* case we will get a fatal error writing the pipe, which */
10283             /* would otherwise lose us the PGN.                       */
10284             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10285             /* output during GameEnds should never be fatal anymore   */
10286             if (gameMode == MachinePlaysWhite ||
10287                 gameMode == MachinePlaysBlack ||
10288                 gameMode == TwoMachinesPlay ||
10289                 gameMode == IcsPlayingWhite ||
10290                 gameMode == IcsPlayingBlack ||
10291                 gameMode == BeginningOfGame) {
10292                 char buf[MSG_SIZ];
10293                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10294                         resultDetails);
10295                 if (first.pr != NoProc) {
10296                     SendToProgram(buf, &first);
10297                 }
10298                 if (second.pr != NoProc &&
10299                     gameMode == TwoMachinesPlay) {
10300                     SendToProgram(buf, &second);
10301                 }
10302             }
10303         }
10304
10305         if (appData.icsActive) {
10306             if (appData.quietPlay &&
10307                 (gameMode == IcsPlayingWhite ||
10308                  gameMode == IcsPlayingBlack)) {
10309                 SendToICS(ics_prefix);
10310                 SendToICS("set shout 1\n");
10311             }
10312             nextGameMode = IcsIdle;
10313             ics_user_moved = FALSE;
10314             /* clean up premove.  It's ugly when the game has ended and the
10315              * premove highlights are still on the board.
10316              */
10317             if (gotPremove) {
10318               gotPremove = FALSE;
10319               ClearPremoveHighlights();
10320               DrawPosition(FALSE, boards[currentMove]);
10321             }
10322             if (whosays == GE_ICS) {
10323                 switch (result) {
10324                 case WhiteWins:
10325                     if (gameMode == IcsPlayingWhite)
10326                         PlayIcsWinSound();
10327                     else if(gameMode == IcsPlayingBlack)
10328                         PlayIcsLossSound();
10329                     break;
10330                 case BlackWins:
10331                     if (gameMode == IcsPlayingBlack)
10332                         PlayIcsWinSound();
10333                     else if(gameMode == IcsPlayingWhite)
10334                         PlayIcsLossSound();
10335                     break;
10336                 case GameIsDrawn:
10337                     PlayIcsDrawSound();
10338                     break;
10339                 default:
10340                     PlayIcsUnfinishedSound();
10341                 }
10342             }
10343         } else if (gameMode == EditGame ||
10344                    gameMode == PlayFromGameFile ||
10345                    gameMode == AnalyzeMode ||
10346                    gameMode == AnalyzeFile) {
10347             nextGameMode = gameMode;
10348         } else {
10349             nextGameMode = EndOfGame;
10350         }
10351         pausing = FALSE;
10352         ModeHighlight();
10353     } else {
10354         nextGameMode = gameMode;
10355     }
10356
10357     if (appData.noChessProgram) {
10358         gameMode = nextGameMode;
10359         ModeHighlight();
10360         endingGame = 0; /* [HGM] crash */
10361         return;
10362     }
10363
10364     if (first.reuse) {
10365         /* Put first chess program into idle state */
10366         if (first.pr != NoProc &&
10367             (gameMode == MachinePlaysWhite ||
10368              gameMode == MachinePlaysBlack ||
10369              gameMode == TwoMachinesPlay ||
10370              gameMode == IcsPlayingWhite ||
10371              gameMode == IcsPlayingBlack ||
10372              gameMode == BeginningOfGame)) {
10373             SendToProgram("force\n", &first);
10374             if (first.usePing) {
10375               char buf[MSG_SIZ];
10376               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10377               SendToProgram(buf, &first);
10378             }
10379         }
10380     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10381         /* Kill off first chess program */
10382         if (first.isr != NULL)
10383           RemoveInputSource(first.isr);
10384         first.isr = NULL;
10385
10386         if (first.pr != NoProc) {
10387             ExitAnalyzeMode();
10388             DoSleep( appData.delayBeforeQuit );
10389             SendToProgram("quit\n", &first);
10390             DoSleep( appData.delayAfterQuit );
10391             DestroyChildProcess(first.pr, first.useSigterm);
10392         }
10393         first.pr = NoProc;
10394     }
10395     if (second.reuse) {
10396         /* Put second chess program into idle state */
10397         if (second.pr != NoProc &&
10398             gameMode == TwoMachinesPlay) {
10399             SendToProgram("force\n", &second);
10400             if (second.usePing) {
10401               char buf[MSG_SIZ];
10402               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10403               SendToProgram(buf, &second);
10404             }
10405         }
10406     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10407         /* Kill off second chess program */
10408         if (second.isr != NULL)
10409           RemoveInputSource(second.isr);
10410         second.isr = NULL;
10411
10412         if (second.pr != NoProc) {
10413             DoSleep( appData.delayBeforeQuit );
10414             SendToProgram("quit\n", &second);
10415             DoSleep( appData.delayAfterQuit );
10416             DestroyChildProcess(second.pr, second.useSigterm);
10417         }
10418         second.pr = NoProc;
10419     }
10420
10421     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10422         char resChar = '=';
10423         switch (result) {
10424         case WhiteWins:
10425           resChar = '+';
10426           if (first.twoMachinesColor[0] == 'w') {
10427             first.matchWins++;
10428           } else {
10429             second.matchWins++;
10430           }
10431           break;
10432         case BlackWins:
10433           resChar = '-';
10434           if (first.twoMachinesColor[0] == 'b') {
10435             first.matchWins++;
10436           } else {
10437             second.matchWins++;
10438           }
10439           break;
10440         case GameUnfinished:
10441           resChar = ' ';
10442         default:
10443           break;
10444         }
10445
10446         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10447         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10448             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10449             ReserveGame(nextGame, resChar); // sets nextGame
10450             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10451             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10452         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10453
10454         if (nextGame <= appData.matchGames && !abortMatch) {
10455             gameMode = nextGameMode;
10456             matchGame = nextGame; // this will be overruled in tourney mode!
10457             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10458             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10459             endingGame = 0; /* [HGM] crash */
10460             return;
10461         } else {
10462             gameMode = nextGameMode;
10463             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10464                      first.tidy, second.tidy,
10465                      first.matchWins, second.matchWins,
10466                      appData.matchGames - (first.matchWins + second.matchWins));
10467             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10468             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10469             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10470                 first.twoMachinesColor = "black\n";
10471                 second.twoMachinesColor = "white\n";
10472             } else {
10473                 first.twoMachinesColor = "white\n";
10474                 second.twoMachinesColor = "black\n";
10475             }
10476         }
10477     }
10478     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10479         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10480       ExitAnalyzeMode();
10481     gameMode = nextGameMode;
10482     ModeHighlight();
10483     endingGame = 0;  /* [HGM] crash */
10484     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10485         if(matchMode == TRUE) { // match through command line: exit with or without popup
10486             if(ranking) {
10487                 ToNrEvent(forwardMostMove);
10488                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10489                 else ExitEvent(0);
10490             } else DisplayFatalError(buf, 0, 0);
10491         } else { // match through menu; just stop, with or without popup
10492             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10493             ModeHighlight();
10494             if(ranking){
10495                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10496             } else DisplayNote(buf);
10497       }
10498       if(ranking) free(ranking);
10499     }
10500 }
10501
10502 /* Assumes program was just initialized (initString sent).
10503    Leaves program in force mode. */
10504 void
10505 FeedMovesToProgram(cps, upto)
10506      ChessProgramState *cps;
10507      int upto;
10508 {
10509     int i;
10510
10511     if (appData.debugMode)
10512       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10513               startedFromSetupPosition ? "position and " : "",
10514               backwardMostMove, upto, cps->which);
10515     if(currentlyInitializedVariant != gameInfo.variant) {
10516       char buf[MSG_SIZ];
10517         // [HGM] variantswitch: make engine aware of new variant
10518         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10519                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10520         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10521         SendToProgram(buf, cps);
10522         currentlyInitializedVariant = gameInfo.variant;
10523     }
10524     SendToProgram("force\n", cps);
10525     if (startedFromSetupPosition) {
10526         SendBoard(cps, backwardMostMove);
10527     if (appData.debugMode) {
10528         fprintf(debugFP, "feedMoves\n");
10529     }
10530     }
10531     for (i = backwardMostMove; i < upto; i++) {
10532         SendMoveToProgram(i, cps);
10533     }
10534 }
10535
10536
10537 int
10538 ResurrectChessProgram()
10539 {
10540      /* The chess program may have exited.
10541         If so, restart it and feed it all the moves made so far. */
10542     static int doInit = 0;
10543
10544     if (appData.noChessProgram) return 1;
10545
10546     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10547         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10548         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10549         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10550     } else {
10551         if (first.pr != NoProc) return 1;
10552         StartChessProgram(&first);
10553     }
10554     InitChessProgram(&first, FALSE);
10555     FeedMovesToProgram(&first, currentMove);
10556
10557     if (!first.sendTime) {
10558         /* can't tell gnuchess what its clock should read,
10559            so we bow to its notion. */
10560         ResetClocks();
10561         timeRemaining[0][currentMove] = whiteTimeRemaining;
10562         timeRemaining[1][currentMove] = blackTimeRemaining;
10563     }
10564
10565     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10566                 appData.icsEngineAnalyze) && first.analysisSupport) {
10567       SendToProgram("analyze\n", &first);
10568       first.analyzing = TRUE;
10569     }
10570     return 1;
10571 }
10572
10573 /*
10574  * Button procedures
10575  */
10576 void
10577 Reset(redraw, init)
10578      int redraw, init;
10579 {
10580     int i;
10581
10582     if (appData.debugMode) {
10583         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10584                 redraw, init, gameMode);
10585     }
10586     CleanupTail(); // [HGM] vari: delete any stored variations
10587     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10588     pausing = pauseExamInvalid = FALSE;
10589     startedFromSetupPosition = blackPlaysFirst = FALSE;
10590     firstMove = TRUE;
10591     whiteFlag = blackFlag = FALSE;
10592     userOfferedDraw = FALSE;
10593     hintRequested = bookRequested = FALSE;
10594     first.maybeThinking = FALSE;
10595     second.maybeThinking = FALSE;
10596     first.bookSuspend = FALSE; // [HGM] book
10597     second.bookSuspend = FALSE;
10598     thinkOutput[0] = NULLCHAR;
10599     lastHint[0] = NULLCHAR;
10600     ClearGameInfo(&gameInfo);
10601     gameInfo.variant = StringToVariant(appData.variant);
10602     ics_user_moved = ics_clock_paused = FALSE;
10603     ics_getting_history = H_FALSE;
10604     ics_gamenum = -1;
10605     white_holding[0] = black_holding[0] = NULLCHAR;
10606     ClearProgramStats();
10607     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10608
10609     ResetFrontEnd();
10610     ClearHighlights();
10611     flipView = appData.flipView;
10612     ClearPremoveHighlights();
10613     gotPremove = FALSE;
10614     alarmSounded = FALSE;
10615
10616     GameEnds(EndOfFile, NULL, GE_PLAYER);
10617     if(appData.serverMovesName != NULL) {
10618         /* [HGM] prepare to make moves file for broadcasting */
10619         clock_t t = clock();
10620         if(serverMoves != NULL) fclose(serverMoves);
10621         serverMoves = fopen(appData.serverMovesName, "r");
10622         if(serverMoves != NULL) {
10623             fclose(serverMoves);
10624             /* delay 15 sec before overwriting, so all clients can see end */
10625             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10626         }
10627         serverMoves = fopen(appData.serverMovesName, "w");
10628     }
10629
10630     ExitAnalyzeMode();
10631     gameMode = BeginningOfGame;
10632     ModeHighlight();
10633     if(appData.icsActive) gameInfo.variant = VariantNormal;
10634     currentMove = forwardMostMove = backwardMostMove = 0;
10635     InitPosition(redraw);
10636     for (i = 0; i < MAX_MOVES; i++) {
10637         if (commentList[i] != NULL) {
10638             free(commentList[i]);
10639             commentList[i] = NULL;
10640         }
10641     }
10642     ResetClocks();
10643     timeRemaining[0][0] = whiteTimeRemaining;
10644     timeRemaining[1][0] = blackTimeRemaining;
10645
10646     if (first.pr == NULL) {
10647         StartChessProgram(&first);
10648     }
10649     if (init) {
10650             InitChessProgram(&first, startedFromSetupPosition);
10651     }
10652     DisplayTitle("");
10653     DisplayMessage("", "");
10654     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10655     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10656 }
10657
10658 void
10659 AutoPlayGameLoop()
10660 {
10661     for (;;) {
10662         if (!AutoPlayOneMove())
10663           return;
10664         if (matchMode || appData.timeDelay == 0)
10665           continue;
10666         if (appData.timeDelay < 0)
10667           return;
10668         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10669         break;
10670     }
10671 }
10672
10673
10674 int
10675 AutoPlayOneMove()
10676 {
10677     int fromX, fromY, toX, toY;
10678
10679     if (appData.debugMode) {
10680       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10681     }
10682
10683     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10684       return FALSE;
10685
10686     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10687       pvInfoList[currentMove].depth = programStats.depth;
10688       pvInfoList[currentMove].score = programStats.score;
10689       pvInfoList[currentMove].time  = 0;
10690       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10691     }
10692
10693     if (currentMove >= forwardMostMove) {
10694       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10695 //      gameMode = EndOfGame;
10696 //      ModeHighlight();
10697
10698       /* [AS] Clear current move marker at the end of a game */
10699       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10700
10701       return FALSE;
10702     }
10703
10704     toX = moveList[currentMove][2] - AAA;
10705     toY = moveList[currentMove][3] - ONE;
10706
10707     if (moveList[currentMove][1] == '@') {
10708         if (appData.highlightLastMove) {
10709             SetHighlights(-1, -1, toX, toY);
10710         }
10711     } else {
10712         fromX = moveList[currentMove][0] - AAA;
10713         fromY = moveList[currentMove][1] - ONE;
10714
10715         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10716
10717         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10718
10719         if (appData.highlightLastMove) {
10720             SetHighlights(fromX, fromY, toX, toY);
10721         }
10722     }
10723     DisplayMove(currentMove);
10724     SendMoveToProgram(currentMove++, &first);
10725     DisplayBothClocks();
10726     DrawPosition(FALSE, boards[currentMove]);
10727     // [HGM] PV info: always display, routine tests if empty
10728     DisplayComment(currentMove - 1, commentList[currentMove]);
10729     return TRUE;
10730 }
10731
10732
10733 int
10734 LoadGameOneMove(readAhead)
10735      ChessMove readAhead;
10736 {
10737     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10738     char promoChar = NULLCHAR;
10739     ChessMove moveType;
10740     char move[MSG_SIZ];
10741     char *p, *q;
10742
10743     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10744         gameMode != AnalyzeMode && gameMode != Training) {
10745         gameFileFP = NULL;
10746         return FALSE;
10747     }
10748
10749     yyboardindex = forwardMostMove;
10750     if (readAhead != EndOfFile) {
10751       moveType = readAhead;
10752     } else {
10753       if (gameFileFP == NULL)
10754           return FALSE;
10755       moveType = (ChessMove) Myylex();
10756     }
10757
10758     done = FALSE;
10759     switch (moveType) {
10760       case Comment:
10761         if (appData.debugMode)
10762           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10763         p = yy_text;
10764
10765         /* append the comment but don't display it */
10766         AppendComment(currentMove, p, FALSE);
10767         return TRUE;
10768
10769       case WhiteCapturesEnPassant:
10770       case BlackCapturesEnPassant:
10771       case WhitePromotion:
10772       case BlackPromotion:
10773       case WhiteNonPromotion:
10774       case BlackNonPromotion:
10775       case NormalMove:
10776       case WhiteKingSideCastle:
10777       case WhiteQueenSideCastle:
10778       case BlackKingSideCastle:
10779       case BlackQueenSideCastle:
10780       case WhiteKingSideCastleWild:
10781       case WhiteQueenSideCastleWild:
10782       case BlackKingSideCastleWild:
10783       case BlackQueenSideCastleWild:
10784       /* PUSH Fabien */
10785       case WhiteHSideCastleFR:
10786       case WhiteASideCastleFR:
10787       case BlackHSideCastleFR:
10788       case BlackASideCastleFR:
10789       /* POP Fabien */
10790         if (appData.debugMode)
10791           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10792         fromX = currentMoveString[0] - AAA;
10793         fromY = currentMoveString[1] - ONE;
10794         toX = currentMoveString[2] - AAA;
10795         toY = currentMoveString[3] - ONE;
10796         promoChar = currentMoveString[4];
10797         break;
10798
10799       case WhiteDrop:
10800       case BlackDrop:
10801         if (appData.debugMode)
10802           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10803         fromX = moveType == WhiteDrop ?
10804           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10805         (int) CharToPiece(ToLower(currentMoveString[0]));
10806         fromY = DROP_RANK;
10807         toX = currentMoveString[2] - AAA;
10808         toY = currentMoveString[3] - ONE;
10809         break;
10810
10811       case WhiteWins:
10812       case BlackWins:
10813       case GameIsDrawn:
10814       case GameUnfinished:
10815         if (appData.debugMode)
10816           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10817         p = strchr(yy_text, '{');
10818         if (p == NULL) p = strchr(yy_text, '(');
10819         if (p == NULL) {
10820             p = yy_text;
10821             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10822         } else {
10823             q = strchr(p, *p == '{' ? '}' : ')');
10824             if (q != NULL) *q = NULLCHAR;
10825             p++;
10826         }
10827         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10828         GameEnds(moveType, p, GE_FILE);
10829         done = TRUE;
10830         if (cmailMsgLoaded) {
10831             ClearHighlights();
10832             flipView = WhiteOnMove(currentMove);
10833             if (moveType == GameUnfinished) flipView = !flipView;
10834             if (appData.debugMode)
10835               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10836         }
10837         break;
10838
10839       case EndOfFile:
10840         if (appData.debugMode)
10841           fprintf(debugFP, "Parser hit end of file\n");
10842         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10843           case MT_NONE:
10844           case MT_CHECK:
10845             break;
10846           case MT_CHECKMATE:
10847           case MT_STAINMATE:
10848             if (WhiteOnMove(currentMove)) {
10849                 GameEnds(BlackWins, "Black mates", GE_FILE);
10850             } else {
10851                 GameEnds(WhiteWins, "White mates", GE_FILE);
10852             }
10853             break;
10854           case MT_STALEMATE:
10855             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10856             break;
10857         }
10858         done = TRUE;
10859         break;
10860
10861       case MoveNumberOne:
10862         if (lastLoadGameStart == GNUChessGame) {
10863             /* GNUChessGames have numbers, but they aren't move numbers */
10864             if (appData.debugMode)
10865               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10866                       yy_text, (int) moveType);
10867             return LoadGameOneMove(EndOfFile); /* tail recursion */
10868         }
10869         /* else fall thru */
10870
10871       case XBoardGame:
10872       case GNUChessGame:
10873       case PGNTag:
10874         /* Reached start of next game in file */
10875         if (appData.debugMode)
10876           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10877         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10878           case MT_NONE:
10879           case MT_CHECK:
10880             break;
10881           case MT_CHECKMATE:
10882           case MT_STAINMATE:
10883             if (WhiteOnMove(currentMove)) {
10884                 GameEnds(BlackWins, "Black mates", GE_FILE);
10885             } else {
10886                 GameEnds(WhiteWins, "White mates", GE_FILE);
10887             }
10888             break;
10889           case MT_STALEMATE:
10890             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10891             break;
10892         }
10893         done = TRUE;
10894         break;
10895
10896       case PositionDiagram:     /* should not happen; ignore */
10897       case ElapsedTime:         /* ignore */
10898       case NAG:                 /* ignore */
10899         if (appData.debugMode)
10900           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10901                   yy_text, (int) moveType);
10902         return LoadGameOneMove(EndOfFile); /* tail recursion */
10903
10904       case IllegalMove:
10905         if (appData.testLegality) {
10906             if (appData.debugMode)
10907               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10908             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10909                     (forwardMostMove / 2) + 1,
10910                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10911             DisplayError(move, 0);
10912             done = TRUE;
10913         } else {
10914             if (appData.debugMode)
10915               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10916                       yy_text, currentMoveString);
10917             fromX = currentMoveString[0] - AAA;
10918             fromY = currentMoveString[1] - ONE;
10919             toX = currentMoveString[2] - AAA;
10920             toY = currentMoveString[3] - ONE;
10921             promoChar = currentMoveString[4];
10922         }
10923         break;
10924
10925       case AmbiguousMove:
10926         if (appData.debugMode)
10927           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10928         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10929                 (forwardMostMove / 2) + 1,
10930                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10931         DisplayError(move, 0);
10932         done = TRUE;
10933         break;
10934
10935       default:
10936       case ImpossibleMove:
10937         if (appData.debugMode)
10938           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10939         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10940                 (forwardMostMove / 2) + 1,
10941                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10942         DisplayError(move, 0);
10943         done = TRUE;
10944         break;
10945     }
10946
10947     if (done) {
10948         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10949             DrawPosition(FALSE, boards[currentMove]);
10950             DisplayBothClocks();
10951             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10952               DisplayComment(currentMove - 1, commentList[currentMove]);
10953         }
10954         (void) StopLoadGameTimer();
10955         gameFileFP = NULL;
10956         cmailOldMove = forwardMostMove;
10957         return FALSE;
10958     } else {
10959         /* currentMoveString is set as a side-effect of yylex */
10960
10961         thinkOutput[0] = NULLCHAR;
10962         MakeMove(fromX, fromY, toX, toY, promoChar);
10963         currentMove = forwardMostMove;
10964         return TRUE;
10965     }
10966 }
10967
10968 /* Load the nth game from the given file */
10969 int
10970 LoadGameFromFile(filename, n, title, useList)
10971      char *filename;
10972      int n;
10973      char *title;
10974      /*Boolean*/ int useList;
10975 {
10976     FILE *f;
10977     char buf[MSG_SIZ];
10978
10979     if (strcmp(filename, "-") == 0) {
10980         f = stdin;
10981         title = "stdin";
10982     } else {
10983         f = fopen(filename, "rb");
10984         if (f == NULL) {
10985           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10986             DisplayError(buf, errno);
10987             return FALSE;
10988         }
10989     }
10990     if (fseek(f, 0, 0) == -1) {
10991         /* f is not seekable; probably a pipe */
10992         useList = FALSE;
10993     }
10994     if (useList && n == 0) {
10995         int error = GameListBuild(f);
10996         if (error) {
10997             DisplayError(_("Cannot build game list"), error);
10998         } else if (!ListEmpty(&gameList) &&
10999                    ((ListGame *) gameList.tailPred)->number > 1) {
11000             GameListPopUp(f, title);
11001             return TRUE;
11002         }
11003         GameListDestroy();
11004         n = 1;
11005     }
11006     if (n == 0) n = 1;
11007     return LoadGame(f, n, title, FALSE);
11008 }
11009
11010
11011 void
11012 MakeRegisteredMove()
11013 {
11014     int fromX, fromY, toX, toY;
11015     char promoChar;
11016     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11017         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11018           case CMAIL_MOVE:
11019           case CMAIL_DRAW:
11020             if (appData.debugMode)
11021               fprintf(debugFP, "Restoring %s for game %d\n",
11022                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11023
11024             thinkOutput[0] = NULLCHAR;
11025             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11026             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11027             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11028             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11029             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11030             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11031             MakeMove(fromX, fromY, toX, toY, promoChar);
11032             ShowMove(fromX, fromY, toX, toY);
11033
11034             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11035               case MT_NONE:
11036               case MT_CHECK:
11037                 break;
11038
11039               case MT_CHECKMATE:
11040               case MT_STAINMATE:
11041                 if (WhiteOnMove(currentMove)) {
11042                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11043                 } else {
11044                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11045                 }
11046                 break;
11047
11048               case MT_STALEMATE:
11049                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11050                 break;
11051             }
11052
11053             break;
11054
11055           case CMAIL_RESIGN:
11056             if (WhiteOnMove(currentMove)) {
11057                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11058             } else {
11059                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11060             }
11061             break;
11062
11063           case CMAIL_ACCEPT:
11064             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11065             break;
11066
11067           default:
11068             break;
11069         }
11070     }
11071
11072     return;
11073 }
11074
11075 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11076 int
11077 CmailLoadGame(f, gameNumber, title, useList)
11078      FILE *f;
11079      int gameNumber;
11080      char *title;
11081      int useList;
11082 {
11083     int retVal;
11084
11085     if (gameNumber > nCmailGames) {
11086         DisplayError(_("No more games in this message"), 0);
11087         return FALSE;
11088     }
11089     if (f == lastLoadGameFP) {
11090         int offset = gameNumber - lastLoadGameNumber;
11091         if (offset == 0) {
11092             cmailMsg[0] = NULLCHAR;
11093             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11094                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11095                 nCmailMovesRegistered--;
11096             }
11097             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11098             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11099                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11100             }
11101         } else {
11102             if (! RegisterMove()) return FALSE;
11103         }
11104     }
11105
11106     retVal = LoadGame(f, gameNumber, title, useList);
11107
11108     /* Make move registered during previous look at this game, if any */
11109     MakeRegisteredMove();
11110
11111     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11112         commentList[currentMove]
11113           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11114         DisplayComment(currentMove - 1, commentList[currentMove]);
11115     }
11116
11117     return retVal;
11118 }
11119
11120 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11121 int
11122 ReloadGame(offset)
11123      int offset;
11124 {
11125     int gameNumber = lastLoadGameNumber + offset;
11126     if (lastLoadGameFP == NULL) {
11127         DisplayError(_("No game has been loaded yet"), 0);
11128         return FALSE;
11129     }
11130     if (gameNumber <= 0) {
11131         DisplayError(_("Can't back up any further"), 0);
11132         return FALSE;
11133     }
11134     if (cmailMsgLoaded) {
11135         return CmailLoadGame(lastLoadGameFP, gameNumber,
11136                              lastLoadGameTitle, lastLoadGameUseList);
11137     } else {
11138         return LoadGame(lastLoadGameFP, gameNumber,
11139                         lastLoadGameTitle, lastLoadGameUseList);
11140     }
11141 }
11142
11143 int keys[EmptySquare+1];
11144
11145 int
11146 PositionMatches(Board b1, Board b2)
11147 {
11148     int r, f, sum=0;
11149     switch(appData.searchMode) {
11150         case 1: return CompareWithRights(b1, b2);
11151         case 2:
11152             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11153                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11154             }
11155             return TRUE;
11156         case 3:
11157             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11158               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11159                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11160             }
11161             return sum==0;
11162         case 4:
11163             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11164                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11165             }
11166             return sum==0;
11167     }
11168     return TRUE;
11169 }
11170
11171 GameInfo dummyInfo;
11172
11173 int GameContainsPosition(FILE *f, ListGame *lg)
11174 {
11175     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11176     int fromX, fromY, toX, toY;
11177     char promoChar;
11178     static int initDone=FALSE;
11179
11180     if(!initDone) {
11181         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11182         initDone = TRUE;
11183     }
11184     dummyInfo.variant = VariantNormal;
11185     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11186     dummyInfo.whiteRating = 0;
11187     dummyInfo.blackRating = 0;
11188     FREE(dummyInfo.date); dummyInfo.date = NULL;
11189     fseek(f, lg->offset, 0);
11190     yynewfile(f);
11191     CopyBoard(boards[scratch], initialPosition); // default start position
11192     while(1) {
11193         yyboardindex = scratch + (plyNr&1);
11194       quickFlag = 1;
11195         next = Myylex();
11196       quickFlag = 0;
11197         switch(next) {
11198             case PGNTag:
11199                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11200 #if 0
11201                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11202                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11203 #else
11204                 // do it ourselves avoiding malloc
11205                 { char *p = yy_text+1, *q;
11206                   while(!isdigit(*p) && !isalpha(*p)) p++;
11207                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11208                   *p = NULLCHAR;
11209                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11210                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11211                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11212                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11213                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11214                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11215                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11216                 }
11217 #endif
11218             default:
11219                 continue;
11220
11221             case XBoardGame:
11222             case GNUChessGame:
11223                 if(plyNr) return -1; // after we have seen moves, this is for new game
11224               continue;
11225
11226             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11227             case ImpossibleMove:
11228             case WhiteWins: // game ends here with these four
11229             case BlackWins:
11230             case GameIsDrawn:
11231             case GameUnfinished:
11232                 return -1;
11233
11234             case IllegalMove:
11235                 if(appData.testLegality) return -1;
11236             case WhiteCapturesEnPassant:
11237             case BlackCapturesEnPassant:
11238             case WhitePromotion:
11239             case BlackPromotion:
11240             case WhiteNonPromotion:
11241             case BlackNonPromotion:
11242             case NormalMove:
11243             case WhiteKingSideCastle:
11244             case WhiteQueenSideCastle:
11245             case BlackKingSideCastle:
11246             case BlackQueenSideCastle:
11247             case WhiteKingSideCastleWild:
11248             case WhiteQueenSideCastleWild:
11249             case BlackKingSideCastleWild:
11250             case BlackQueenSideCastleWild:
11251             case WhiteHSideCastleFR:
11252             case WhiteASideCastleFR:
11253             case BlackHSideCastleFR:
11254             case BlackASideCastleFR:
11255                 fromX = currentMoveString[0] - AAA;
11256                 fromY = currentMoveString[1] - ONE;
11257                 toX = currentMoveString[2] - AAA;
11258                 toY = currentMoveString[3] - ONE;
11259                 promoChar = currentMoveString[4];
11260                 break;
11261             case WhiteDrop:
11262             case BlackDrop:
11263                 fromX = next == WhiteDrop ?
11264                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11265                   (int) CharToPiece(ToLower(currentMoveString[0]));
11266                 fromY = DROP_RANK;
11267                 toX = currentMoveString[2] - AAA;
11268                 toY = currentMoveString[3] - ONE;
11269                 break;
11270         }
11271         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11272         if(plyNr == 0) { // but first figure out variant and initial position
11273             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11274             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11275             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11276             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11277             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11278             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11279         }
11280         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11281         plyNr++;
11282         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11283         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11284     }
11285 }
11286
11287 /* Load the nth game from open file f */
11288 int
11289 LoadGame(f, gameNumber, title, useList)
11290      FILE *f;
11291      int gameNumber;
11292      char *title;
11293      int useList;
11294 {
11295     ChessMove cm;
11296     char buf[MSG_SIZ];
11297     int gn = gameNumber;
11298     ListGame *lg = NULL;
11299     int numPGNTags = 0;
11300     int err, pos = -1;
11301     GameMode oldGameMode;
11302     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11303
11304     if (appData.debugMode)
11305         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11306
11307     if (gameMode == Training )
11308         SetTrainingModeOff();
11309
11310     oldGameMode = gameMode;
11311     if (gameMode != BeginningOfGame) {
11312       Reset(FALSE, TRUE);
11313     }
11314
11315     gameFileFP = f;
11316     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11317         fclose(lastLoadGameFP);
11318     }
11319
11320     if (useList) {
11321         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11322
11323         if (lg) {
11324             fseek(f, lg->offset, 0);
11325             GameListHighlight(gameNumber);
11326             pos = lg->position;
11327             gn = 1;
11328         }
11329         else {
11330             DisplayError(_("Game number out of range"), 0);
11331             return FALSE;
11332         }
11333     } else {
11334         GameListDestroy();
11335         if (fseek(f, 0, 0) == -1) {
11336             if (f == lastLoadGameFP ?
11337                 gameNumber == lastLoadGameNumber + 1 :
11338                 gameNumber == 1) {
11339                 gn = 1;
11340             } else {
11341                 DisplayError(_("Can't seek on game file"), 0);
11342                 return FALSE;
11343             }
11344         }
11345     }
11346     lastLoadGameFP = f;
11347     lastLoadGameNumber = gameNumber;
11348     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11349     lastLoadGameUseList = useList;
11350
11351     yynewfile(f);
11352
11353     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11354       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11355                 lg->gameInfo.black);
11356             DisplayTitle(buf);
11357     } else if (*title != NULLCHAR) {
11358         if (gameNumber > 1) {
11359           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11360             DisplayTitle(buf);
11361         } else {
11362             DisplayTitle(title);
11363         }
11364     }
11365
11366     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11367         gameMode = PlayFromGameFile;
11368         ModeHighlight();
11369     }
11370
11371     currentMove = forwardMostMove = backwardMostMove = 0;
11372     CopyBoard(boards[0], initialPosition);
11373     StopClocks();
11374
11375     /*
11376      * Skip the first gn-1 games in the file.
11377      * Also skip over anything that precedes an identifiable
11378      * start of game marker, to avoid being confused by
11379      * garbage at the start of the file.  Currently
11380      * recognized start of game markers are the move number "1",
11381      * the pattern "gnuchess .* game", the pattern
11382      * "^[#;%] [^ ]* game file", and a PGN tag block.
11383      * A game that starts with one of the latter two patterns
11384      * will also have a move number 1, possibly
11385      * following a position diagram.
11386      * 5-4-02: Let's try being more lenient and allowing a game to
11387      * start with an unnumbered move.  Does that break anything?
11388      */
11389     cm = lastLoadGameStart = EndOfFile;
11390     while (gn > 0) {
11391         yyboardindex = forwardMostMove;
11392         cm = (ChessMove) Myylex();
11393         switch (cm) {
11394           case EndOfFile:
11395             if (cmailMsgLoaded) {
11396                 nCmailGames = CMAIL_MAX_GAMES - gn;
11397             } else {
11398                 Reset(TRUE, TRUE);
11399                 DisplayError(_("Game not found in file"), 0);
11400             }
11401             return FALSE;
11402
11403           case GNUChessGame:
11404           case XBoardGame:
11405             gn--;
11406             lastLoadGameStart = cm;
11407             break;
11408
11409           case MoveNumberOne:
11410             switch (lastLoadGameStart) {
11411               case GNUChessGame:
11412               case XBoardGame:
11413               case PGNTag:
11414                 break;
11415               case MoveNumberOne:
11416               case EndOfFile:
11417                 gn--;           /* count this game */
11418                 lastLoadGameStart = cm;
11419                 break;
11420               default:
11421                 /* impossible */
11422                 break;
11423             }
11424             break;
11425
11426           case PGNTag:
11427             switch (lastLoadGameStart) {
11428               case GNUChessGame:
11429               case PGNTag:
11430               case MoveNumberOne:
11431               case EndOfFile:
11432                 gn--;           /* count this game */
11433                 lastLoadGameStart = cm;
11434                 break;
11435               case XBoardGame:
11436                 lastLoadGameStart = cm; /* game counted already */
11437                 break;
11438               default:
11439                 /* impossible */
11440                 break;
11441             }
11442             if (gn > 0) {
11443                 do {
11444                     yyboardindex = forwardMostMove;
11445                     cm = (ChessMove) Myylex();
11446                 } while (cm == PGNTag || cm == Comment);
11447             }
11448             break;
11449
11450           case WhiteWins:
11451           case BlackWins:
11452           case GameIsDrawn:
11453             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11454                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11455                     != CMAIL_OLD_RESULT) {
11456                     nCmailResults ++ ;
11457                     cmailResult[  CMAIL_MAX_GAMES
11458                                 - gn - 1] = CMAIL_OLD_RESULT;
11459                 }
11460             }
11461             break;
11462
11463           case NormalMove:
11464             /* Only a NormalMove can be at the start of a game
11465              * without a position diagram. */
11466             if (lastLoadGameStart == EndOfFile ) {
11467               gn--;
11468               lastLoadGameStart = MoveNumberOne;
11469             }
11470             break;
11471
11472           default:
11473             break;
11474         }
11475     }
11476
11477     if (appData.debugMode)
11478       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11479
11480     if (cm == XBoardGame) {
11481         /* Skip any header junk before position diagram and/or move 1 */
11482         for (;;) {
11483             yyboardindex = forwardMostMove;
11484             cm = (ChessMove) Myylex();
11485
11486             if (cm == EndOfFile ||
11487                 cm == GNUChessGame || cm == XBoardGame) {
11488                 /* Empty game; pretend end-of-file and handle later */
11489                 cm = EndOfFile;
11490                 break;
11491             }
11492
11493             if (cm == MoveNumberOne || cm == PositionDiagram ||
11494                 cm == PGNTag || cm == Comment)
11495               break;
11496         }
11497     } else if (cm == GNUChessGame) {
11498         if (gameInfo.event != NULL) {
11499             free(gameInfo.event);
11500         }
11501         gameInfo.event = StrSave(yy_text);
11502     }
11503
11504     startedFromSetupPosition = FALSE;
11505     while (cm == PGNTag) {
11506         if (appData.debugMode)
11507           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11508         err = ParsePGNTag(yy_text, &gameInfo);
11509         if (!err) numPGNTags++;
11510
11511         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11512         if(gameInfo.variant != oldVariant) {
11513             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11514             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11515             InitPosition(TRUE);
11516             oldVariant = gameInfo.variant;
11517             if (appData.debugMode)
11518               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11519         }
11520
11521
11522         if (gameInfo.fen != NULL) {
11523           Board initial_position;
11524           startedFromSetupPosition = TRUE;
11525           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11526             Reset(TRUE, TRUE);
11527             DisplayError(_("Bad FEN position in file"), 0);
11528             return FALSE;
11529           }
11530           CopyBoard(boards[0], initial_position);
11531           if (blackPlaysFirst) {
11532             currentMove = forwardMostMove = backwardMostMove = 1;
11533             CopyBoard(boards[1], initial_position);
11534             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11535             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11536             timeRemaining[0][1] = whiteTimeRemaining;
11537             timeRemaining[1][1] = blackTimeRemaining;
11538             if (commentList[0] != NULL) {
11539               commentList[1] = commentList[0];
11540               commentList[0] = NULL;
11541             }
11542           } else {
11543             currentMove = forwardMostMove = backwardMostMove = 0;
11544           }
11545           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11546           {   int i;
11547               initialRulePlies = FENrulePlies;
11548               for( i=0; i< nrCastlingRights; i++ )
11549                   initialRights[i] = initial_position[CASTLING][i];
11550           }
11551           yyboardindex = forwardMostMove;
11552           free(gameInfo.fen);
11553           gameInfo.fen = NULL;
11554         }
11555
11556         yyboardindex = forwardMostMove;
11557         cm = (ChessMove) Myylex();
11558
11559         /* Handle comments interspersed among the tags */
11560         while (cm == Comment) {
11561             char *p;
11562             if (appData.debugMode)
11563               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11564             p = yy_text;
11565             AppendComment(currentMove, p, FALSE);
11566             yyboardindex = forwardMostMove;
11567             cm = (ChessMove) Myylex();
11568         }
11569     }
11570
11571     /* don't rely on existence of Event tag since if game was
11572      * pasted from clipboard the Event tag may not exist
11573      */
11574     if (numPGNTags > 0){
11575         char *tags;
11576         if (gameInfo.variant == VariantNormal) {
11577           VariantClass v = StringToVariant(gameInfo.event);
11578           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11579           if(v < VariantShogi) gameInfo.variant = v;
11580         }
11581         if (!matchMode) {
11582           if( appData.autoDisplayTags ) {
11583             tags = PGNTags(&gameInfo);
11584             TagsPopUp(tags, CmailMsg());
11585             free(tags);
11586           }
11587         }
11588     } else {
11589         /* Make something up, but don't display it now */
11590         SetGameInfo();
11591         TagsPopDown();
11592     }
11593
11594     if (cm == PositionDiagram) {
11595         int i, j;
11596         char *p;
11597         Board initial_position;
11598
11599         if (appData.debugMode)
11600           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11601
11602         if (!startedFromSetupPosition) {
11603             p = yy_text;
11604             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11605               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11606                 switch (*p) {
11607                   case '{':
11608                   case '[':
11609                   case '-':
11610                   case ' ':
11611                   case '\t':
11612                   case '\n':
11613                   case '\r':
11614                     break;
11615                   default:
11616                     initial_position[i][j++] = CharToPiece(*p);
11617                     break;
11618                 }
11619             while (*p == ' ' || *p == '\t' ||
11620                    *p == '\n' || *p == '\r') p++;
11621
11622             if (strncmp(p, "black", strlen("black"))==0)
11623               blackPlaysFirst = TRUE;
11624             else
11625               blackPlaysFirst = FALSE;
11626             startedFromSetupPosition = TRUE;
11627
11628             CopyBoard(boards[0], initial_position);
11629             if (blackPlaysFirst) {
11630                 currentMove = forwardMostMove = backwardMostMove = 1;
11631                 CopyBoard(boards[1], initial_position);
11632                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11633                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11634                 timeRemaining[0][1] = whiteTimeRemaining;
11635                 timeRemaining[1][1] = blackTimeRemaining;
11636                 if (commentList[0] != NULL) {
11637                     commentList[1] = commentList[0];
11638                     commentList[0] = NULL;
11639                 }
11640             } else {
11641                 currentMove = forwardMostMove = backwardMostMove = 0;
11642             }
11643         }
11644         yyboardindex = forwardMostMove;
11645         cm = (ChessMove) Myylex();
11646     }
11647
11648     if (first.pr == NoProc) {
11649         StartChessProgram(&first);
11650     }
11651     InitChessProgram(&first, FALSE);
11652     SendToProgram("force\n", &first);
11653     if (startedFromSetupPosition) {
11654         SendBoard(&first, forwardMostMove);
11655     if (appData.debugMode) {
11656         fprintf(debugFP, "Load Game\n");
11657     }
11658         DisplayBothClocks();
11659     }
11660
11661     /* [HGM] server: flag to write setup moves in broadcast file as one */
11662     loadFlag = appData.suppressLoadMoves;
11663
11664     while (cm == Comment) {
11665         char *p;
11666         if (appData.debugMode)
11667           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11668         p = yy_text;
11669         AppendComment(currentMove, p, FALSE);
11670         yyboardindex = forwardMostMove;
11671         cm = (ChessMove) Myylex();
11672     }
11673
11674     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11675         cm == WhiteWins || cm == BlackWins ||
11676         cm == GameIsDrawn || cm == GameUnfinished) {
11677         DisplayMessage("", _("No moves in game"));
11678         if (cmailMsgLoaded) {
11679             if (appData.debugMode)
11680               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11681             ClearHighlights();
11682             flipView = FALSE;
11683         }
11684         DrawPosition(FALSE, boards[currentMove]);
11685         DisplayBothClocks();
11686         gameMode = EditGame;
11687         ModeHighlight();
11688         gameFileFP = NULL;
11689         cmailOldMove = 0;
11690         return TRUE;
11691     }
11692
11693     // [HGM] PV info: routine tests if comment empty
11694     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11695         DisplayComment(currentMove - 1, commentList[currentMove]);
11696     }
11697     if (!matchMode && appData.timeDelay != 0)
11698       DrawPosition(FALSE, boards[currentMove]);
11699
11700     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11701       programStats.ok_to_send = 1;
11702     }
11703
11704     /* if the first token after the PGN tags is a move
11705      * and not move number 1, retrieve it from the parser
11706      */
11707     if (cm != MoveNumberOne)
11708         LoadGameOneMove(cm);
11709
11710     /* load the remaining moves from the file */
11711     while (LoadGameOneMove(EndOfFile)) {
11712       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11713       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11714     }
11715
11716     /* rewind to the start of the game */
11717     currentMove = backwardMostMove;
11718
11719     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11720
11721     if (oldGameMode == AnalyzeFile ||
11722         oldGameMode == AnalyzeMode) {
11723       AnalyzeFileEvent();
11724     }
11725
11726     if (!matchMode && pos >= 0) {
11727         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11728     } else
11729     if (matchMode || appData.timeDelay == 0) {
11730       ToEndEvent();
11731     } else if (appData.timeDelay > 0) {
11732       AutoPlayGameLoop();
11733     }
11734
11735     if (appData.debugMode)
11736         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11737
11738     loadFlag = 0; /* [HGM] true game starts */
11739     return TRUE;
11740 }
11741
11742 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11743 int
11744 ReloadPosition(offset)
11745      int offset;
11746 {
11747     int positionNumber = lastLoadPositionNumber + offset;
11748     if (lastLoadPositionFP == NULL) {
11749         DisplayError(_("No position has been loaded yet"), 0);
11750         return FALSE;
11751     }
11752     if (positionNumber <= 0) {
11753         DisplayError(_("Can't back up any further"), 0);
11754         return FALSE;
11755     }
11756     return LoadPosition(lastLoadPositionFP, positionNumber,
11757                         lastLoadPositionTitle);
11758 }
11759
11760 /* Load the nth position from the given file */
11761 int
11762 LoadPositionFromFile(filename, n, title)
11763      char *filename;
11764      int n;
11765      char *title;
11766 {
11767     FILE *f;
11768     char buf[MSG_SIZ];
11769
11770     if (strcmp(filename, "-") == 0) {
11771         return LoadPosition(stdin, n, "stdin");
11772     } else {
11773         f = fopen(filename, "rb");
11774         if (f == NULL) {
11775             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11776             DisplayError(buf, errno);
11777             return FALSE;
11778         } else {
11779             return LoadPosition(f, n, title);
11780         }
11781     }
11782 }
11783
11784 /* Load the nth position from the given open file, and close it */
11785 int
11786 LoadPosition(f, positionNumber, title)
11787      FILE *f;
11788      int positionNumber;
11789      char *title;
11790 {
11791     char *p, line[MSG_SIZ];
11792     Board initial_position;
11793     int i, j, fenMode, pn;
11794
11795     if (gameMode == Training )
11796         SetTrainingModeOff();
11797
11798     if (gameMode != BeginningOfGame) {
11799         Reset(FALSE, TRUE);
11800     }
11801     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11802         fclose(lastLoadPositionFP);
11803     }
11804     if (positionNumber == 0) positionNumber = 1;
11805     lastLoadPositionFP = f;
11806     lastLoadPositionNumber = positionNumber;
11807     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11808     if (first.pr == NoProc) {
11809       StartChessProgram(&first);
11810       InitChessProgram(&first, FALSE);
11811     }
11812     pn = positionNumber;
11813     if (positionNumber < 0) {
11814         /* Negative position number means to seek to that byte offset */
11815         if (fseek(f, -positionNumber, 0) == -1) {
11816             DisplayError(_("Can't seek on position file"), 0);
11817             return FALSE;
11818         };
11819         pn = 1;
11820     } else {
11821         if (fseek(f, 0, 0) == -1) {
11822             if (f == lastLoadPositionFP ?
11823                 positionNumber == lastLoadPositionNumber + 1 :
11824                 positionNumber == 1) {
11825                 pn = 1;
11826             } else {
11827                 DisplayError(_("Can't seek on position file"), 0);
11828                 return FALSE;
11829             }
11830         }
11831     }
11832     /* See if this file is FEN or old-style xboard */
11833     if (fgets(line, MSG_SIZ, f) == NULL) {
11834         DisplayError(_("Position not found in file"), 0);
11835         return FALSE;
11836     }
11837     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11838     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11839
11840     if (pn >= 2) {
11841         if (fenMode || line[0] == '#') pn--;
11842         while (pn > 0) {
11843             /* skip positions before number pn */
11844             if (fgets(line, MSG_SIZ, f) == NULL) {
11845                 Reset(TRUE, TRUE);
11846                 DisplayError(_("Position not found in file"), 0);
11847                 return FALSE;
11848             }
11849             if (fenMode || line[0] == '#') pn--;
11850         }
11851     }
11852
11853     if (fenMode) {
11854         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11855             DisplayError(_("Bad FEN position in file"), 0);
11856             return FALSE;
11857         }
11858     } else {
11859         (void) fgets(line, MSG_SIZ, f);
11860         (void) fgets(line, MSG_SIZ, f);
11861
11862         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11863             (void) fgets(line, MSG_SIZ, f);
11864             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11865                 if (*p == ' ')
11866                   continue;
11867                 initial_position[i][j++] = CharToPiece(*p);
11868             }
11869         }
11870
11871         blackPlaysFirst = FALSE;
11872         if (!feof(f)) {
11873             (void) fgets(line, MSG_SIZ, f);
11874             if (strncmp(line, "black", strlen("black"))==0)
11875               blackPlaysFirst = TRUE;
11876         }
11877     }
11878     startedFromSetupPosition = TRUE;
11879
11880     SendToProgram("force\n", &first);
11881     CopyBoard(boards[0], initial_position);
11882     if (blackPlaysFirst) {
11883         currentMove = forwardMostMove = backwardMostMove = 1;
11884         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11885         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11886         CopyBoard(boards[1], initial_position);
11887         DisplayMessage("", _("Black to play"));
11888     } else {
11889         currentMove = forwardMostMove = backwardMostMove = 0;
11890         DisplayMessage("", _("White to play"));
11891     }
11892     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11893     SendBoard(&first, forwardMostMove);
11894     if (appData.debugMode) {
11895 int i, j;
11896   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11897   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11898         fprintf(debugFP, "Load Position\n");
11899     }
11900
11901     if (positionNumber > 1) {
11902       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11903         DisplayTitle(line);
11904     } else {
11905         DisplayTitle(title);
11906     }
11907     gameMode = EditGame;
11908     ModeHighlight();
11909     ResetClocks();
11910     timeRemaining[0][1] = whiteTimeRemaining;
11911     timeRemaining[1][1] = blackTimeRemaining;
11912     DrawPosition(FALSE, boards[currentMove]);
11913
11914     return TRUE;
11915 }
11916
11917
11918 void
11919 CopyPlayerNameIntoFileName(dest, src)
11920      char **dest, *src;
11921 {
11922     while (*src != NULLCHAR && *src != ',') {
11923         if (*src == ' ') {
11924             *(*dest)++ = '_';
11925             src++;
11926         } else {
11927             *(*dest)++ = *src++;
11928         }
11929     }
11930 }
11931
11932 char *DefaultFileName(ext)
11933      char *ext;
11934 {
11935     static char def[MSG_SIZ];
11936     char *p;
11937
11938     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11939         p = def;
11940         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11941         *p++ = '-';
11942         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11943         *p++ = '.';
11944         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11945     } else {
11946         def[0] = NULLCHAR;
11947     }
11948     return def;
11949 }
11950
11951 /* Save the current game to the given file */
11952 int
11953 SaveGameToFile(filename, append)
11954      char *filename;
11955      int append;
11956 {
11957     FILE *f;
11958     char buf[MSG_SIZ];
11959     int result, i, t,tot=0;
11960
11961     if (strcmp(filename, "-") == 0) {
11962         return SaveGame(stdout, 0, NULL);
11963     } else {
11964         for(i=0; i<10; i++) { // upto 10 tries
11965              f = fopen(filename, append ? "a" : "w");
11966              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
11967              if(f || errno != 13) break;
11968              DoSleep(t = 5 + random()%11); // wait 5-15 msec
11969              tot += t;
11970         }
11971         if (f == NULL) {
11972             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11973             DisplayError(buf, errno);
11974             return FALSE;
11975         } else {
11976             safeStrCpy(buf, lastMsg, MSG_SIZ);
11977             DisplayMessage(_("Waiting for access to save file"), "");
11978             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11979             DisplayMessage(_("Saving game"), "");
11980             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11981             result = SaveGame(f, 0, NULL);
11982             DisplayMessage(buf, "");
11983             return result;
11984         }
11985     }
11986 }
11987
11988 char *
11989 SavePart(str)
11990      char *str;
11991 {
11992     static char buf[MSG_SIZ];
11993     char *p;
11994
11995     p = strchr(str, ' ');
11996     if (p == NULL) return str;
11997     strncpy(buf, str, p - str);
11998     buf[p - str] = NULLCHAR;
11999     return buf;
12000 }
12001
12002 #define PGN_MAX_LINE 75
12003
12004 #define PGN_SIDE_WHITE  0
12005 #define PGN_SIDE_BLACK  1
12006
12007 /* [AS] */
12008 static int FindFirstMoveOutOfBook( int side )
12009 {
12010     int result = -1;
12011
12012     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12013         int index = backwardMostMove;
12014         int has_book_hit = 0;
12015
12016         if( (index % 2) != side ) {
12017             index++;
12018         }
12019
12020         while( index < forwardMostMove ) {
12021             /* Check to see if engine is in book */
12022             int depth = pvInfoList[index].depth;
12023             int score = pvInfoList[index].score;
12024             int in_book = 0;
12025
12026             if( depth <= 2 ) {
12027                 in_book = 1;
12028             }
12029             else if( score == 0 && depth == 63 ) {
12030                 in_book = 1; /* Zappa */
12031             }
12032             else if( score == 2 && depth == 99 ) {
12033                 in_book = 1; /* Abrok */
12034             }
12035
12036             has_book_hit += in_book;
12037
12038             if( ! in_book ) {
12039                 result = index;
12040
12041                 break;
12042             }
12043
12044             index += 2;
12045         }
12046     }
12047
12048     return result;
12049 }
12050
12051 /* [AS] */
12052 void GetOutOfBookInfo( char * buf )
12053 {
12054     int oob[2];
12055     int i;
12056     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12057
12058     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12059     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12060
12061     *buf = '\0';
12062
12063     if( oob[0] >= 0 || oob[1] >= 0 ) {
12064         for( i=0; i<2; i++ ) {
12065             int idx = oob[i];
12066
12067             if( idx >= 0 ) {
12068                 if( i > 0 && oob[0] >= 0 ) {
12069                     strcat( buf, "   " );
12070                 }
12071
12072                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12073                 sprintf( buf+strlen(buf), "%s%.2f",
12074                     pvInfoList[idx].score >= 0 ? "+" : "",
12075                     pvInfoList[idx].score / 100.0 );
12076             }
12077         }
12078     }
12079 }
12080
12081 /* Save game in PGN style and close the file */
12082 int
12083 SaveGamePGN(f)
12084      FILE *f;
12085 {
12086     int i, offset, linelen, newblock;
12087     time_t tm;
12088 //    char *movetext;
12089     char numtext[32];
12090     int movelen, numlen, blank;
12091     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12092
12093     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12094
12095     tm = time((time_t *) NULL);
12096
12097     PrintPGNTags(f, &gameInfo);
12098
12099     if (backwardMostMove > 0 || startedFromSetupPosition) {
12100         char *fen = PositionToFEN(backwardMostMove, NULL);
12101         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12102         fprintf(f, "\n{--------------\n");
12103         PrintPosition(f, backwardMostMove);
12104         fprintf(f, "--------------}\n");
12105         free(fen);
12106     }
12107     else {
12108         /* [AS] Out of book annotation */
12109         if( appData.saveOutOfBookInfo ) {
12110             char buf[64];
12111
12112             GetOutOfBookInfo( buf );
12113
12114             if( buf[0] != '\0' ) {
12115                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12116             }
12117         }
12118
12119         fprintf(f, "\n");
12120     }
12121
12122     i = backwardMostMove;
12123     linelen = 0;
12124     newblock = TRUE;
12125
12126     while (i < forwardMostMove) {
12127         /* Print comments preceding this move */
12128         if (commentList[i] != NULL) {
12129             if (linelen > 0) fprintf(f, "\n");
12130             fprintf(f, "%s", commentList[i]);
12131             linelen = 0;
12132             newblock = TRUE;
12133         }
12134
12135         /* Format move number */
12136         if ((i % 2) == 0)
12137           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12138         else
12139           if (newblock)
12140             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12141           else
12142             numtext[0] = NULLCHAR;
12143
12144         numlen = strlen(numtext);
12145         newblock = FALSE;
12146
12147         /* Print move number */
12148         blank = linelen > 0 && numlen > 0;
12149         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12150             fprintf(f, "\n");
12151             linelen = 0;
12152             blank = 0;
12153         }
12154         if (blank) {
12155             fprintf(f, " ");
12156             linelen++;
12157         }
12158         fprintf(f, "%s", numtext);
12159         linelen += numlen;
12160
12161         /* Get move */
12162         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12163         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12164
12165         /* Print move */
12166         blank = linelen > 0 && movelen > 0;
12167         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12168             fprintf(f, "\n");
12169             linelen = 0;
12170             blank = 0;
12171         }
12172         if (blank) {
12173             fprintf(f, " ");
12174             linelen++;
12175         }
12176         fprintf(f, "%s", move_buffer);
12177         linelen += movelen;
12178
12179         /* [AS] Add PV info if present */
12180         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12181             /* [HGM] add time */
12182             char buf[MSG_SIZ]; int seconds;
12183
12184             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12185
12186             if( seconds <= 0)
12187               buf[0] = 0;
12188             else
12189               if( seconds < 30 )
12190                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12191               else
12192                 {
12193                   seconds = (seconds + 4)/10; // round to full seconds
12194                   if( seconds < 60 )
12195                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12196                   else
12197                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12198                 }
12199
12200             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12201                       pvInfoList[i].score >= 0 ? "+" : "",
12202                       pvInfoList[i].score / 100.0,
12203                       pvInfoList[i].depth,
12204                       buf );
12205
12206             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12207
12208             /* Print score/depth */
12209             blank = linelen > 0 && movelen > 0;
12210             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12211                 fprintf(f, "\n");
12212                 linelen = 0;
12213                 blank = 0;
12214             }
12215             if (blank) {
12216                 fprintf(f, " ");
12217                 linelen++;
12218             }
12219             fprintf(f, "%s", move_buffer);
12220             linelen += movelen;
12221         }
12222
12223         i++;
12224     }
12225
12226     /* Start a new line */
12227     if (linelen > 0) fprintf(f, "\n");
12228
12229     /* Print comments after last move */
12230     if (commentList[i] != NULL) {
12231         fprintf(f, "%s\n", commentList[i]);
12232     }
12233
12234     /* Print result */
12235     if (gameInfo.resultDetails != NULL &&
12236         gameInfo.resultDetails[0] != NULLCHAR) {
12237         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12238                 PGNResult(gameInfo.result));
12239     } else {
12240         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12241     }
12242
12243     fclose(f);
12244     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12245     return TRUE;
12246 }
12247
12248 /* Save game in old style and close the file */
12249 int
12250 SaveGameOldStyle(f)
12251      FILE *f;
12252 {
12253     int i, offset;
12254     time_t tm;
12255
12256     tm = time((time_t *) NULL);
12257
12258     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12259     PrintOpponents(f);
12260
12261     if (backwardMostMove > 0 || startedFromSetupPosition) {
12262         fprintf(f, "\n[--------------\n");
12263         PrintPosition(f, backwardMostMove);
12264         fprintf(f, "--------------]\n");
12265     } else {
12266         fprintf(f, "\n");
12267     }
12268
12269     i = backwardMostMove;
12270     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12271
12272     while (i < forwardMostMove) {
12273         if (commentList[i] != NULL) {
12274             fprintf(f, "[%s]\n", commentList[i]);
12275         }
12276
12277         if ((i % 2) == 1) {
12278             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12279             i++;
12280         } else {
12281             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12282             i++;
12283             if (commentList[i] != NULL) {
12284                 fprintf(f, "\n");
12285                 continue;
12286             }
12287             if (i >= forwardMostMove) {
12288                 fprintf(f, "\n");
12289                 break;
12290             }
12291             fprintf(f, "%s\n", parseList[i]);
12292             i++;
12293         }
12294     }
12295
12296     if (commentList[i] != NULL) {
12297         fprintf(f, "[%s]\n", commentList[i]);
12298     }
12299
12300     /* This isn't really the old style, but it's close enough */
12301     if (gameInfo.resultDetails != NULL &&
12302         gameInfo.resultDetails[0] != NULLCHAR) {
12303         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12304                 gameInfo.resultDetails);
12305     } else {
12306         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12307     }
12308
12309     fclose(f);
12310     return TRUE;
12311 }
12312
12313 /* Save the current game to open file f and close the file */
12314 int
12315 SaveGame(f, dummy, dummy2)
12316      FILE *f;
12317      int dummy;
12318      char *dummy2;
12319 {
12320     if (gameMode == EditPosition) EditPositionDone(TRUE);
12321     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12322     if (appData.oldSaveStyle)
12323       return SaveGameOldStyle(f);
12324     else
12325       return SaveGamePGN(f);
12326 }
12327
12328 /* Save the current position to the given file */
12329 int
12330 SavePositionToFile(filename)
12331      char *filename;
12332 {
12333     FILE *f;
12334     char buf[MSG_SIZ];
12335
12336     if (strcmp(filename, "-") == 0) {
12337         return SavePosition(stdout, 0, NULL);
12338     } else {
12339         f = fopen(filename, "a");
12340         if (f == NULL) {
12341             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12342             DisplayError(buf, errno);
12343             return FALSE;
12344         } else {
12345             safeStrCpy(buf, lastMsg, MSG_SIZ);
12346             DisplayMessage(_("Waiting for access to save file"), "");
12347             flock(fileno(f), LOCK_EX); // [HGM] lock
12348             DisplayMessage(_("Saving position"), "");
12349             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12350             SavePosition(f, 0, NULL);
12351             DisplayMessage(buf, "");
12352             return TRUE;
12353         }
12354     }
12355 }
12356
12357 /* Save the current position to the given open file and close the file */
12358 int
12359 SavePosition(f, dummy, dummy2)
12360      FILE *f;
12361      int dummy;
12362      char *dummy2;
12363 {
12364     time_t tm;
12365     char *fen;
12366
12367     if (gameMode == EditPosition) EditPositionDone(TRUE);
12368     if (appData.oldSaveStyle) {
12369         tm = time((time_t *) NULL);
12370
12371         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12372         PrintOpponents(f);
12373         fprintf(f, "[--------------\n");
12374         PrintPosition(f, currentMove);
12375         fprintf(f, "--------------]\n");
12376     } else {
12377         fen = PositionToFEN(currentMove, NULL);
12378         fprintf(f, "%s\n", fen);
12379         free(fen);
12380     }
12381     fclose(f);
12382     return TRUE;
12383 }
12384
12385 void
12386 ReloadCmailMsgEvent(unregister)
12387      int unregister;
12388 {
12389 #if !WIN32
12390     static char *inFilename = NULL;
12391     static char *outFilename;
12392     int i;
12393     struct stat inbuf, outbuf;
12394     int status;
12395
12396     /* Any registered moves are unregistered if unregister is set, */
12397     /* i.e. invoked by the signal handler */
12398     if (unregister) {
12399         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12400             cmailMoveRegistered[i] = FALSE;
12401             if (cmailCommentList[i] != NULL) {
12402                 free(cmailCommentList[i]);
12403                 cmailCommentList[i] = NULL;
12404             }
12405         }
12406         nCmailMovesRegistered = 0;
12407     }
12408
12409     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12410         cmailResult[i] = CMAIL_NOT_RESULT;
12411     }
12412     nCmailResults = 0;
12413
12414     if (inFilename == NULL) {
12415         /* Because the filenames are static they only get malloced once  */
12416         /* and they never get freed                                      */
12417         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12418         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12419
12420         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12421         sprintf(outFilename, "%s.out", appData.cmailGameName);
12422     }
12423
12424     status = stat(outFilename, &outbuf);
12425     if (status < 0) {
12426         cmailMailedMove = FALSE;
12427     } else {
12428         status = stat(inFilename, &inbuf);
12429         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12430     }
12431
12432     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12433        counts the games, notes how each one terminated, etc.
12434
12435        It would be nice to remove this kludge and instead gather all
12436        the information while building the game list.  (And to keep it
12437        in the game list nodes instead of having a bunch of fixed-size
12438        parallel arrays.)  Note this will require getting each game's
12439        termination from the PGN tags, as the game list builder does
12440        not process the game moves.  --mann
12441        */
12442     cmailMsgLoaded = TRUE;
12443     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12444
12445     /* Load first game in the file or popup game menu */
12446     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12447
12448 #endif /* !WIN32 */
12449     return;
12450 }
12451
12452 int
12453 RegisterMove()
12454 {
12455     FILE *f;
12456     char string[MSG_SIZ];
12457
12458     if (   cmailMailedMove
12459         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12460         return TRUE;            /* Allow free viewing  */
12461     }
12462
12463     /* Unregister move to ensure that we don't leave RegisterMove        */
12464     /* with the move registered when the conditions for registering no   */
12465     /* longer hold                                                       */
12466     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12467         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12468         nCmailMovesRegistered --;
12469
12470         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12471           {
12472               free(cmailCommentList[lastLoadGameNumber - 1]);
12473               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12474           }
12475     }
12476
12477     if (cmailOldMove == -1) {
12478         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12479         return FALSE;
12480     }
12481
12482     if (currentMove > cmailOldMove + 1) {
12483         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12484         return FALSE;
12485     }
12486
12487     if (currentMove < cmailOldMove) {
12488         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12489         return FALSE;
12490     }
12491
12492     if (forwardMostMove > currentMove) {
12493         /* Silently truncate extra moves */
12494         TruncateGame();
12495     }
12496
12497     if (   (currentMove == cmailOldMove + 1)
12498         || (   (currentMove == cmailOldMove)
12499             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12500                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12501         if (gameInfo.result != GameUnfinished) {
12502             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12503         }
12504
12505         if (commentList[currentMove] != NULL) {
12506             cmailCommentList[lastLoadGameNumber - 1]
12507               = StrSave(commentList[currentMove]);
12508         }
12509         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12510
12511         if (appData.debugMode)
12512           fprintf(debugFP, "Saving %s for game %d\n",
12513                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12514
12515         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12516
12517         f = fopen(string, "w");
12518         if (appData.oldSaveStyle) {
12519             SaveGameOldStyle(f); /* also closes the file */
12520
12521             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12522             f = fopen(string, "w");
12523             SavePosition(f, 0, NULL); /* also closes the file */
12524         } else {
12525             fprintf(f, "{--------------\n");
12526             PrintPosition(f, currentMove);
12527             fprintf(f, "--------------}\n\n");
12528
12529             SaveGame(f, 0, NULL); /* also closes the file*/
12530         }
12531
12532         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12533         nCmailMovesRegistered ++;
12534     } else if (nCmailGames == 1) {
12535         DisplayError(_("You have not made a move yet"), 0);
12536         return FALSE;
12537     }
12538
12539     return TRUE;
12540 }
12541
12542 void
12543 MailMoveEvent()
12544 {
12545 #if !WIN32
12546     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12547     FILE *commandOutput;
12548     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12549     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12550     int nBuffers;
12551     int i;
12552     int archived;
12553     char *arcDir;
12554
12555     if (! cmailMsgLoaded) {
12556         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12557         return;
12558     }
12559
12560     if (nCmailGames == nCmailResults) {
12561         DisplayError(_("No unfinished games"), 0);
12562         return;
12563     }
12564
12565 #if CMAIL_PROHIBIT_REMAIL
12566     if (cmailMailedMove) {
12567       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);
12568         DisplayError(msg, 0);
12569         return;
12570     }
12571 #endif
12572
12573     if (! (cmailMailedMove || RegisterMove())) return;
12574
12575     if (   cmailMailedMove
12576         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12577       snprintf(string, MSG_SIZ, partCommandString,
12578                appData.debugMode ? " -v" : "", appData.cmailGameName);
12579         commandOutput = popen(string, "r");
12580
12581         if (commandOutput == NULL) {
12582             DisplayError(_("Failed to invoke cmail"), 0);
12583         } else {
12584             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12585                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12586             }
12587             if (nBuffers > 1) {
12588                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12589                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12590                 nBytes = MSG_SIZ - 1;
12591             } else {
12592                 (void) memcpy(msg, buffer, nBytes);
12593             }
12594             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12595
12596             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12597                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12598
12599                 archived = TRUE;
12600                 for (i = 0; i < nCmailGames; i ++) {
12601                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12602                         archived = FALSE;
12603                     }
12604                 }
12605                 if (   archived
12606                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12607                         != NULL)) {
12608                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12609                            arcDir,
12610                            appData.cmailGameName,
12611                            gameInfo.date);
12612                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12613                     cmailMsgLoaded = FALSE;
12614                 }
12615             }
12616
12617             DisplayInformation(msg);
12618             pclose(commandOutput);
12619         }
12620     } else {
12621         if ((*cmailMsg) != '\0') {
12622             DisplayInformation(cmailMsg);
12623         }
12624     }
12625
12626     return;
12627 #endif /* !WIN32 */
12628 }
12629
12630 char *
12631 CmailMsg()
12632 {
12633 #if WIN32
12634     return NULL;
12635 #else
12636     int  prependComma = 0;
12637     char number[5];
12638     char string[MSG_SIZ];       /* Space for game-list */
12639     int  i;
12640
12641     if (!cmailMsgLoaded) return "";
12642
12643     if (cmailMailedMove) {
12644       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12645     } else {
12646         /* Create a list of games left */
12647       snprintf(string, MSG_SIZ, "[");
12648         for (i = 0; i < nCmailGames; i ++) {
12649             if (! (   cmailMoveRegistered[i]
12650                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12651                 if (prependComma) {
12652                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12653                 } else {
12654                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12655                     prependComma = 1;
12656                 }
12657
12658                 strcat(string, number);
12659             }
12660         }
12661         strcat(string, "]");
12662
12663         if (nCmailMovesRegistered + nCmailResults == 0) {
12664             switch (nCmailGames) {
12665               case 1:
12666                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12667                 break;
12668
12669               case 2:
12670                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12671                 break;
12672
12673               default:
12674                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12675                          nCmailGames);
12676                 break;
12677             }
12678         } else {
12679             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12680               case 1:
12681                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12682                          string);
12683                 break;
12684
12685               case 0:
12686                 if (nCmailResults == nCmailGames) {
12687                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12688                 } else {
12689                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12690                 }
12691                 break;
12692
12693               default:
12694                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12695                          string);
12696             }
12697         }
12698     }
12699     return cmailMsg;
12700 #endif /* WIN32 */
12701 }
12702
12703 void
12704 ResetGameEvent()
12705 {
12706     if (gameMode == Training)
12707       SetTrainingModeOff();
12708
12709     Reset(TRUE, TRUE);
12710     cmailMsgLoaded = FALSE;
12711     if (appData.icsActive) {
12712       SendToICS(ics_prefix);
12713       SendToICS("refresh\n");
12714     }
12715 }
12716
12717 void
12718 ExitEvent(status)
12719      int status;
12720 {
12721     exiting++;
12722     if (exiting > 2) {
12723       /* Give up on clean exit */
12724       exit(status);
12725     }
12726     if (exiting > 1) {
12727       /* Keep trying for clean exit */
12728       return;
12729     }
12730
12731     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12732
12733     if (telnetISR != NULL) {
12734       RemoveInputSource(telnetISR);
12735     }
12736     if (icsPR != NoProc) {
12737       DestroyChildProcess(icsPR, TRUE);
12738     }
12739
12740     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12741     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12742
12743     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12744     /* make sure this other one finishes before killing it!                  */
12745     if(endingGame) { int count = 0;
12746         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12747         while(endingGame && count++ < 10) DoSleep(1);
12748         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12749     }
12750
12751     /* Kill off chess programs */
12752     if (first.pr != NoProc) {
12753         ExitAnalyzeMode();
12754
12755         DoSleep( appData.delayBeforeQuit );
12756         SendToProgram("quit\n", &first);
12757         DoSleep( appData.delayAfterQuit );
12758         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12759     }
12760     if (second.pr != NoProc) {
12761         DoSleep( appData.delayBeforeQuit );
12762         SendToProgram("quit\n", &second);
12763         DoSleep( appData.delayAfterQuit );
12764         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12765     }
12766     if (first.isr != NULL) {
12767         RemoveInputSource(first.isr);
12768     }
12769     if (second.isr != NULL) {
12770         RemoveInputSource(second.isr);
12771     }
12772
12773     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12774     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12775
12776     ShutDownFrontEnd();
12777     exit(status);
12778 }
12779
12780 void
12781 PauseEvent()
12782 {
12783     if (appData.debugMode)
12784         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12785     if (pausing) {
12786         pausing = FALSE;
12787         ModeHighlight();
12788         if (gameMode == MachinePlaysWhite ||
12789             gameMode == MachinePlaysBlack) {
12790             StartClocks();
12791         } else {
12792             DisplayBothClocks();
12793         }
12794         if (gameMode == PlayFromGameFile) {
12795             if (appData.timeDelay >= 0)
12796                 AutoPlayGameLoop();
12797         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12798             Reset(FALSE, TRUE);
12799             SendToICS(ics_prefix);
12800             SendToICS("refresh\n");
12801         } else if (currentMove < forwardMostMove) {
12802             ForwardInner(forwardMostMove);
12803         }
12804         pauseExamInvalid = FALSE;
12805     } else {
12806         switch (gameMode) {
12807           default:
12808             return;
12809           case IcsExamining:
12810             pauseExamForwardMostMove = forwardMostMove;
12811             pauseExamInvalid = FALSE;
12812             /* fall through */
12813           case IcsObserving:
12814           case IcsPlayingWhite:
12815           case IcsPlayingBlack:
12816             pausing = TRUE;
12817             ModeHighlight();
12818             return;
12819           case PlayFromGameFile:
12820             (void) StopLoadGameTimer();
12821             pausing = TRUE;
12822             ModeHighlight();
12823             break;
12824           case BeginningOfGame:
12825             if (appData.icsActive) return;
12826             /* else fall through */
12827           case MachinePlaysWhite:
12828           case MachinePlaysBlack:
12829           case TwoMachinesPlay:
12830             if (forwardMostMove == 0)
12831               return;           /* don't pause if no one has moved */
12832             if ((gameMode == MachinePlaysWhite &&
12833                  !WhiteOnMove(forwardMostMove)) ||
12834                 (gameMode == MachinePlaysBlack &&
12835                  WhiteOnMove(forwardMostMove))) {
12836                 StopClocks();
12837             }
12838             pausing = TRUE;
12839             ModeHighlight();
12840             break;
12841         }
12842     }
12843 }
12844
12845 void
12846 EditCommentEvent()
12847 {
12848     char title[MSG_SIZ];
12849
12850     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12851       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12852     } else {
12853       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12854                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12855                parseList[currentMove - 1]);
12856     }
12857
12858     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12859 }
12860
12861
12862 void
12863 EditTagsEvent()
12864 {
12865     char *tags = PGNTags(&gameInfo);
12866     bookUp = FALSE;
12867     EditTagsPopUp(tags, NULL);
12868     free(tags);
12869 }
12870
12871 void
12872 AnalyzeModeEvent()
12873 {
12874     if (appData.noChessProgram || gameMode == AnalyzeMode)
12875       return;
12876
12877     if (gameMode != AnalyzeFile) {
12878         if (!appData.icsEngineAnalyze) {
12879                EditGameEvent();
12880                if (gameMode != EditGame) return;
12881         }
12882         ResurrectChessProgram();
12883         SendToProgram("analyze\n", &first);
12884         first.analyzing = TRUE;
12885         /*first.maybeThinking = TRUE;*/
12886         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12887         EngineOutputPopUp();
12888     }
12889     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12890     pausing = FALSE;
12891     ModeHighlight();
12892     SetGameInfo();
12893
12894     StartAnalysisClock();
12895     GetTimeMark(&lastNodeCountTime);
12896     lastNodeCount = 0;
12897 }
12898
12899 void
12900 AnalyzeFileEvent()
12901 {
12902     if (appData.noChessProgram || gameMode == AnalyzeFile)
12903       return;
12904
12905     if (gameMode != AnalyzeMode) {
12906         EditGameEvent();
12907         if (gameMode != EditGame) return;
12908         ResurrectChessProgram();
12909         SendToProgram("analyze\n", &first);
12910         first.analyzing = TRUE;
12911         /*first.maybeThinking = TRUE;*/
12912         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12913         EngineOutputPopUp();
12914     }
12915     gameMode = AnalyzeFile;
12916     pausing = FALSE;
12917     ModeHighlight();
12918     SetGameInfo();
12919
12920     StartAnalysisClock();
12921     GetTimeMark(&lastNodeCountTime);
12922     lastNodeCount = 0;
12923     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12924 }
12925
12926 void
12927 MachineWhiteEvent()
12928 {
12929     char buf[MSG_SIZ];
12930     char *bookHit = NULL;
12931
12932     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12933       return;
12934
12935
12936     if (gameMode == PlayFromGameFile ||
12937         gameMode == TwoMachinesPlay  ||
12938         gameMode == Training         ||
12939         gameMode == AnalyzeMode      ||
12940         gameMode == EndOfGame)
12941         EditGameEvent();
12942
12943     if (gameMode == EditPosition)
12944         EditPositionDone(TRUE);
12945
12946     if (!WhiteOnMove(currentMove)) {
12947         DisplayError(_("It is not White's turn"), 0);
12948         return;
12949     }
12950
12951     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12952       ExitAnalyzeMode();
12953
12954     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12955         gameMode == AnalyzeFile)
12956         TruncateGame();
12957
12958     ResurrectChessProgram();    /* in case it isn't running */
12959     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12960         gameMode = MachinePlaysWhite;
12961         ResetClocks();
12962     } else
12963     gameMode = MachinePlaysWhite;
12964     pausing = FALSE;
12965     ModeHighlight();
12966     SetGameInfo();
12967     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12968     DisplayTitle(buf);
12969     if (first.sendName) {
12970       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12971       SendToProgram(buf, &first);
12972     }
12973     if (first.sendTime) {
12974       if (first.useColors) {
12975         SendToProgram("black\n", &first); /*gnu kludge*/
12976       }
12977       SendTimeRemaining(&first, TRUE);
12978     }
12979     if (first.useColors) {
12980       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12981     }
12982     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12983     SetMachineThinkingEnables();
12984     first.maybeThinking = TRUE;
12985     StartClocks();
12986     firstMove = FALSE;
12987
12988     if (appData.autoFlipView && !flipView) {
12989       flipView = !flipView;
12990       DrawPosition(FALSE, NULL);
12991       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12992     }
12993
12994     if(bookHit) { // [HGM] book: simulate book reply
12995         static char bookMove[MSG_SIZ]; // a bit generous?
12996
12997         programStats.nodes = programStats.depth = programStats.time =
12998         programStats.score = programStats.got_only_move = 0;
12999         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13000
13001         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13002         strcat(bookMove, bookHit);
13003         HandleMachineMove(bookMove, &first);
13004     }
13005 }
13006
13007 void
13008 MachineBlackEvent()
13009 {
13010   char buf[MSG_SIZ];
13011   char *bookHit = NULL;
13012
13013     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13014         return;
13015
13016
13017     if (gameMode == PlayFromGameFile ||
13018         gameMode == TwoMachinesPlay  ||
13019         gameMode == Training         ||
13020         gameMode == AnalyzeMode      ||
13021         gameMode == EndOfGame)
13022         EditGameEvent();
13023
13024     if (gameMode == EditPosition)
13025         EditPositionDone(TRUE);
13026
13027     if (WhiteOnMove(currentMove)) {
13028         DisplayError(_("It is not Black's turn"), 0);
13029         return;
13030     }
13031
13032     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13033       ExitAnalyzeMode();
13034
13035     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13036         gameMode == AnalyzeFile)
13037         TruncateGame();
13038
13039     ResurrectChessProgram();    /* in case it isn't running */
13040     gameMode = MachinePlaysBlack;
13041     pausing = FALSE;
13042     ModeHighlight();
13043     SetGameInfo();
13044     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13045     DisplayTitle(buf);
13046     if (first.sendName) {
13047       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13048       SendToProgram(buf, &first);
13049     }
13050     if (first.sendTime) {
13051       if (first.useColors) {
13052         SendToProgram("white\n", &first); /*gnu kludge*/
13053       }
13054       SendTimeRemaining(&first, FALSE);
13055     }
13056     if (first.useColors) {
13057       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13058     }
13059     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13060     SetMachineThinkingEnables();
13061     first.maybeThinking = TRUE;
13062     StartClocks();
13063
13064     if (appData.autoFlipView && flipView) {
13065       flipView = !flipView;
13066       DrawPosition(FALSE, NULL);
13067       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13068     }
13069     if(bookHit) { // [HGM] book: simulate book reply
13070         static char bookMove[MSG_SIZ]; // a bit generous?
13071
13072         programStats.nodes = programStats.depth = programStats.time =
13073         programStats.score = programStats.got_only_move = 0;
13074         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13075
13076         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13077         strcat(bookMove, bookHit);
13078         HandleMachineMove(bookMove, &first);
13079     }
13080 }
13081
13082
13083 void
13084 DisplayTwoMachinesTitle()
13085 {
13086     char buf[MSG_SIZ];
13087     if (appData.matchGames > 0) {
13088         if(appData.tourneyFile[0]) {
13089           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13090                    gameInfo.white, gameInfo.black,
13091                    nextGame+1, appData.matchGames+1,
13092                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13093         } else 
13094         if (first.twoMachinesColor[0] == 'w') {
13095           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13096                    gameInfo.white, gameInfo.black,
13097                    first.matchWins, second.matchWins,
13098                    matchGame - 1 - (first.matchWins + second.matchWins));
13099         } else {
13100           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13101                    gameInfo.white, gameInfo.black,
13102                    second.matchWins, first.matchWins,
13103                    matchGame - 1 - (first.matchWins + second.matchWins));
13104         }
13105     } else {
13106       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13107     }
13108     DisplayTitle(buf);
13109 }
13110
13111 void
13112 SettingsMenuIfReady()
13113 {
13114   if (second.lastPing != second.lastPong) {
13115     DisplayMessage("", _("Waiting for second chess program"));
13116     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13117     return;
13118   }
13119   ThawUI();
13120   DisplayMessage("", "");
13121   SettingsPopUp(&second);
13122 }
13123
13124 int
13125 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13126 {
13127     char buf[MSG_SIZ];
13128     if (cps->pr == NULL) {
13129         StartChessProgram(cps);
13130         if (cps->protocolVersion == 1) {
13131           retry();
13132         } else {
13133           /* kludge: allow timeout for initial "feature" command */
13134           FreezeUI();
13135           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13136           DisplayMessage("", buf);
13137           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13138         }
13139         return 1;
13140     }
13141     return 0;
13142 }
13143
13144 void
13145 TwoMachinesEvent P((void))
13146 {
13147     int i;
13148     char buf[MSG_SIZ];
13149     ChessProgramState *onmove;
13150     char *bookHit = NULL;
13151     static int stalling = 0;
13152     TimeMark now;
13153     long wait;
13154
13155     if (appData.noChessProgram) return;
13156
13157     switch (gameMode) {
13158       case TwoMachinesPlay:
13159         return;
13160       case MachinePlaysWhite:
13161       case MachinePlaysBlack:
13162         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13163             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13164             return;
13165         }
13166         /* fall through */
13167       case BeginningOfGame:
13168       case PlayFromGameFile:
13169       case EndOfGame:
13170         EditGameEvent();
13171         if (gameMode != EditGame) return;
13172         break;
13173       case EditPosition:
13174         EditPositionDone(TRUE);
13175         break;
13176       case AnalyzeMode:
13177       case AnalyzeFile:
13178         ExitAnalyzeMode();
13179         break;
13180       case EditGame:
13181       default:
13182         break;
13183     }
13184
13185 //    forwardMostMove = currentMove;
13186     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13187
13188     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13189
13190     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13191     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13192       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13193       return;
13194     }
13195     if(!stalling) {
13196       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13197       SendToProgram("force\n", &second);
13198       stalling = 1;
13199       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13200       return;
13201     }
13202     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13203     if(appData.matchPause>10000 || appData.matchPause<10)
13204                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13205     wait = SubtractTimeMarks(&now, &pauseStart);
13206     if(wait < appData.matchPause) {
13207         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13208         return;
13209     }
13210     stalling = 0;
13211     DisplayMessage("", "");
13212     if (startedFromSetupPosition) {
13213         SendBoard(&second, backwardMostMove);
13214     if (appData.debugMode) {
13215         fprintf(debugFP, "Two Machines\n");
13216     }
13217     }
13218     for (i = backwardMostMove; i < forwardMostMove; i++) {
13219         SendMoveToProgram(i, &second);
13220     }
13221
13222     gameMode = TwoMachinesPlay;
13223     pausing = FALSE;
13224     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13225     SetGameInfo();
13226     DisplayTwoMachinesTitle();
13227     firstMove = TRUE;
13228     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13229         onmove = &first;
13230     } else {
13231         onmove = &second;
13232     }
13233     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13234     SendToProgram(first.computerString, &first);
13235     if (first.sendName) {
13236       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13237       SendToProgram(buf, &first);
13238     }
13239     SendToProgram(second.computerString, &second);
13240     if (second.sendName) {
13241       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13242       SendToProgram(buf, &second);
13243     }
13244
13245     ResetClocks();
13246     if (!first.sendTime || !second.sendTime) {
13247         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13248         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13249     }
13250     if (onmove->sendTime) {
13251       if (onmove->useColors) {
13252         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13253       }
13254       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13255     }
13256     if (onmove->useColors) {
13257       SendToProgram(onmove->twoMachinesColor, onmove);
13258     }
13259     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13260 //    SendToProgram("go\n", onmove);
13261     onmove->maybeThinking = TRUE;
13262     SetMachineThinkingEnables();
13263
13264     StartClocks();
13265
13266     if(bookHit) { // [HGM] book: simulate book reply
13267         static char bookMove[MSG_SIZ]; // a bit generous?
13268
13269         programStats.nodes = programStats.depth = programStats.time =
13270         programStats.score = programStats.got_only_move = 0;
13271         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13272
13273         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13274         strcat(bookMove, bookHit);
13275         savedMessage = bookMove; // args for deferred call
13276         savedState = onmove;
13277         ScheduleDelayedEvent(DeferredBookMove, 1);
13278     }
13279 }
13280
13281 void
13282 TrainingEvent()
13283 {
13284     if (gameMode == Training) {
13285       SetTrainingModeOff();
13286       gameMode = PlayFromGameFile;
13287       DisplayMessage("", _("Training mode off"));
13288     } else {
13289       gameMode = Training;
13290       animateTraining = appData.animate;
13291
13292       /* make sure we are not already at the end of the game */
13293       if (currentMove < forwardMostMove) {
13294         SetTrainingModeOn();
13295         DisplayMessage("", _("Training mode on"));
13296       } else {
13297         gameMode = PlayFromGameFile;
13298         DisplayError(_("Already at end of game"), 0);
13299       }
13300     }
13301     ModeHighlight();
13302 }
13303
13304 void
13305 IcsClientEvent()
13306 {
13307     if (!appData.icsActive) return;
13308     switch (gameMode) {
13309       case IcsPlayingWhite:
13310       case IcsPlayingBlack:
13311       case IcsObserving:
13312       case IcsIdle:
13313       case BeginningOfGame:
13314       case IcsExamining:
13315         return;
13316
13317       case EditGame:
13318         break;
13319
13320       case EditPosition:
13321         EditPositionDone(TRUE);
13322         break;
13323
13324       case AnalyzeMode:
13325       case AnalyzeFile:
13326         ExitAnalyzeMode();
13327         break;
13328
13329       default:
13330         EditGameEvent();
13331         break;
13332     }
13333
13334     gameMode = IcsIdle;
13335     ModeHighlight();
13336     return;
13337 }
13338
13339
13340 void
13341 EditGameEvent()
13342 {
13343     int i;
13344
13345     switch (gameMode) {
13346       case Training:
13347         SetTrainingModeOff();
13348         break;
13349       case MachinePlaysWhite:
13350       case MachinePlaysBlack:
13351       case BeginningOfGame:
13352         SendToProgram("force\n", &first);
13353         SetUserThinkingEnables();
13354         break;
13355       case PlayFromGameFile:
13356         (void) StopLoadGameTimer();
13357         if (gameFileFP != NULL) {
13358             gameFileFP = NULL;
13359         }
13360         break;
13361       case EditPosition:
13362         EditPositionDone(TRUE);
13363         break;
13364       case AnalyzeMode:
13365       case AnalyzeFile:
13366         ExitAnalyzeMode();
13367         SendToProgram("force\n", &first);
13368         break;
13369       case TwoMachinesPlay:
13370         GameEnds(EndOfFile, NULL, GE_PLAYER);
13371         ResurrectChessProgram();
13372         SetUserThinkingEnables();
13373         break;
13374       case EndOfGame:
13375         ResurrectChessProgram();
13376         break;
13377       case IcsPlayingBlack:
13378       case IcsPlayingWhite:
13379         DisplayError(_("Warning: You are still playing a game"), 0);
13380         break;
13381       case IcsObserving:
13382         DisplayError(_("Warning: You are still observing a game"), 0);
13383         break;
13384       case IcsExamining:
13385         DisplayError(_("Warning: You are still examining a game"), 0);
13386         break;
13387       case IcsIdle:
13388         break;
13389       case EditGame:
13390       default:
13391         return;
13392     }
13393
13394     pausing = FALSE;
13395     StopClocks();
13396     first.offeredDraw = second.offeredDraw = 0;
13397
13398     if (gameMode == PlayFromGameFile) {
13399         whiteTimeRemaining = timeRemaining[0][currentMove];
13400         blackTimeRemaining = timeRemaining[1][currentMove];
13401         DisplayTitle("");
13402     }
13403
13404     if (gameMode == MachinePlaysWhite ||
13405         gameMode == MachinePlaysBlack ||
13406         gameMode == TwoMachinesPlay ||
13407         gameMode == EndOfGame) {
13408         i = forwardMostMove;
13409         while (i > currentMove) {
13410             SendToProgram("undo\n", &first);
13411             i--;
13412         }
13413         whiteTimeRemaining = timeRemaining[0][currentMove];
13414         blackTimeRemaining = timeRemaining[1][currentMove];
13415         DisplayBothClocks();
13416         if (whiteFlag || blackFlag) {
13417             whiteFlag = blackFlag = 0;
13418         }
13419         DisplayTitle("");
13420     }
13421
13422     gameMode = EditGame;
13423     ModeHighlight();
13424     SetGameInfo();
13425 }
13426
13427
13428 void
13429 EditPositionEvent()
13430 {
13431     if (gameMode == EditPosition) {
13432         EditGameEvent();
13433         return;
13434     }
13435
13436     EditGameEvent();
13437     if (gameMode != EditGame) return;
13438
13439     gameMode = EditPosition;
13440     ModeHighlight();
13441     SetGameInfo();
13442     if (currentMove > 0)
13443       CopyBoard(boards[0], boards[currentMove]);
13444
13445     blackPlaysFirst = !WhiteOnMove(currentMove);
13446     ResetClocks();
13447     currentMove = forwardMostMove = backwardMostMove = 0;
13448     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13449     DisplayMove(-1);
13450 }
13451
13452 void
13453 ExitAnalyzeMode()
13454 {
13455     /* [DM] icsEngineAnalyze - possible call from other functions */
13456     if (appData.icsEngineAnalyze) {
13457         appData.icsEngineAnalyze = FALSE;
13458
13459         DisplayMessage("",_("Close ICS engine analyze..."));
13460     }
13461     if (first.analysisSupport && first.analyzing) {
13462       SendToProgram("exit\n", &first);
13463       first.analyzing = FALSE;
13464     }
13465     thinkOutput[0] = NULLCHAR;
13466 }
13467
13468 void
13469 EditPositionDone(Boolean fakeRights)
13470 {
13471     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13472
13473     startedFromSetupPosition = TRUE;
13474     InitChessProgram(&first, FALSE);
13475     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13476       boards[0][EP_STATUS] = EP_NONE;
13477       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13478     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13479         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13480         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13481       } else boards[0][CASTLING][2] = NoRights;
13482     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13483         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13484         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13485       } else boards[0][CASTLING][5] = NoRights;
13486     }
13487     SendToProgram("force\n", &first);
13488     if (blackPlaysFirst) {
13489         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13490         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13491         currentMove = forwardMostMove = backwardMostMove = 1;
13492         CopyBoard(boards[1], boards[0]);
13493     } else {
13494         currentMove = forwardMostMove = backwardMostMove = 0;
13495     }
13496     SendBoard(&first, forwardMostMove);
13497     if (appData.debugMode) {
13498         fprintf(debugFP, "EditPosDone\n");
13499     }
13500     DisplayTitle("");
13501     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13502     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13503     gameMode = EditGame;
13504     ModeHighlight();
13505     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13506     ClearHighlights(); /* [AS] */
13507 }
13508
13509 /* Pause for `ms' milliseconds */
13510 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13511 void
13512 TimeDelay(ms)
13513      long ms;
13514 {
13515     TimeMark m1, m2;
13516
13517     GetTimeMark(&m1);
13518     do {
13519         GetTimeMark(&m2);
13520     } while (SubtractTimeMarks(&m2, &m1) < ms);
13521 }
13522
13523 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13524 void
13525 SendMultiLineToICS(buf)
13526      char *buf;
13527 {
13528     char temp[MSG_SIZ+1], *p;
13529     int len;
13530
13531     len = strlen(buf);
13532     if (len > MSG_SIZ)
13533       len = MSG_SIZ;
13534
13535     strncpy(temp, buf, len);
13536     temp[len] = 0;
13537
13538     p = temp;
13539     while (*p) {
13540         if (*p == '\n' || *p == '\r')
13541           *p = ' ';
13542         ++p;
13543     }
13544
13545     strcat(temp, "\n");
13546     SendToICS(temp);
13547     SendToPlayer(temp, strlen(temp));
13548 }
13549
13550 void
13551 SetWhiteToPlayEvent()
13552 {
13553     if (gameMode == EditPosition) {
13554         blackPlaysFirst = FALSE;
13555         DisplayBothClocks();    /* works because currentMove is 0 */
13556     } else if (gameMode == IcsExamining) {
13557         SendToICS(ics_prefix);
13558         SendToICS("tomove white\n");
13559     }
13560 }
13561
13562 void
13563 SetBlackToPlayEvent()
13564 {
13565     if (gameMode == EditPosition) {
13566         blackPlaysFirst = TRUE;
13567         currentMove = 1;        /* kludge */
13568         DisplayBothClocks();
13569         currentMove = 0;
13570     } else if (gameMode == IcsExamining) {
13571         SendToICS(ics_prefix);
13572         SendToICS("tomove black\n");
13573     }
13574 }
13575
13576 void
13577 EditPositionMenuEvent(selection, x, y)
13578      ChessSquare selection;
13579      int x, y;
13580 {
13581     char buf[MSG_SIZ];
13582     ChessSquare piece = boards[0][y][x];
13583
13584     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13585
13586     switch (selection) {
13587       case ClearBoard:
13588         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13589             SendToICS(ics_prefix);
13590             SendToICS("bsetup clear\n");
13591         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13592             SendToICS(ics_prefix);
13593             SendToICS("clearboard\n");
13594         } else {
13595             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13596                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13597                 for (y = 0; y < BOARD_HEIGHT; y++) {
13598                     if (gameMode == IcsExamining) {
13599                         if (boards[currentMove][y][x] != EmptySquare) {
13600                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13601                                     AAA + x, ONE + y);
13602                             SendToICS(buf);
13603                         }
13604                     } else {
13605                         boards[0][y][x] = p;
13606                     }
13607                 }
13608             }
13609         }
13610         if (gameMode == EditPosition) {
13611             DrawPosition(FALSE, boards[0]);
13612         }
13613         break;
13614
13615       case WhitePlay:
13616         SetWhiteToPlayEvent();
13617         break;
13618
13619       case BlackPlay:
13620         SetBlackToPlayEvent();
13621         break;
13622
13623       case EmptySquare:
13624         if (gameMode == IcsExamining) {
13625             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13626             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13627             SendToICS(buf);
13628         } else {
13629             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13630                 if(x == BOARD_LEFT-2) {
13631                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13632                     boards[0][y][1] = 0;
13633                 } else
13634                 if(x == BOARD_RGHT+1) {
13635                     if(y >= gameInfo.holdingsSize) break;
13636                     boards[0][y][BOARD_WIDTH-2] = 0;
13637                 } else break;
13638             }
13639             boards[0][y][x] = EmptySquare;
13640             DrawPosition(FALSE, boards[0]);
13641         }
13642         break;
13643
13644       case PromotePiece:
13645         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13646            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13647             selection = (ChessSquare) (PROMOTED piece);
13648         } else if(piece == EmptySquare) selection = WhiteSilver;
13649         else selection = (ChessSquare)((int)piece - 1);
13650         goto defaultlabel;
13651
13652       case DemotePiece:
13653         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13654            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13655             selection = (ChessSquare) (DEMOTED piece);
13656         } else if(piece == EmptySquare) selection = BlackSilver;
13657         else selection = (ChessSquare)((int)piece + 1);
13658         goto defaultlabel;
13659
13660       case WhiteQueen:
13661       case BlackQueen:
13662         if(gameInfo.variant == VariantShatranj ||
13663            gameInfo.variant == VariantXiangqi  ||
13664            gameInfo.variant == VariantCourier  ||
13665            gameInfo.variant == VariantMakruk     )
13666             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13667         goto defaultlabel;
13668
13669       case WhiteKing:
13670       case BlackKing:
13671         if(gameInfo.variant == VariantXiangqi)
13672             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13673         if(gameInfo.variant == VariantKnightmate)
13674             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13675       default:
13676         defaultlabel:
13677         if (gameMode == IcsExamining) {
13678             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13679             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13680                      PieceToChar(selection), AAA + x, ONE + y);
13681             SendToICS(buf);
13682         } else {
13683             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13684                 int n;
13685                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13686                     n = PieceToNumber(selection - BlackPawn);
13687                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13688                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13689                     boards[0][BOARD_HEIGHT-1-n][1]++;
13690                 } else
13691                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13692                     n = PieceToNumber(selection);
13693                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13694                     boards[0][n][BOARD_WIDTH-1] = selection;
13695                     boards[0][n][BOARD_WIDTH-2]++;
13696                 }
13697             } else
13698             boards[0][y][x] = selection;
13699             DrawPosition(TRUE, boards[0]);
13700         }
13701         break;
13702     }
13703 }
13704
13705
13706 void
13707 DropMenuEvent(selection, x, y)
13708      ChessSquare selection;
13709      int x, y;
13710 {
13711     ChessMove moveType;
13712
13713     switch (gameMode) {
13714       case IcsPlayingWhite:
13715       case MachinePlaysBlack:
13716         if (!WhiteOnMove(currentMove)) {
13717             DisplayMoveError(_("It is Black's turn"));
13718             return;
13719         }
13720         moveType = WhiteDrop;
13721         break;
13722       case IcsPlayingBlack:
13723       case MachinePlaysWhite:
13724         if (WhiteOnMove(currentMove)) {
13725             DisplayMoveError(_("It is White's turn"));
13726             return;
13727         }
13728         moveType = BlackDrop;
13729         break;
13730       case EditGame:
13731         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13732         break;
13733       default:
13734         return;
13735     }
13736
13737     if (moveType == BlackDrop && selection < BlackPawn) {
13738       selection = (ChessSquare) ((int) selection
13739                                  + (int) BlackPawn - (int) WhitePawn);
13740     }
13741     if (boards[currentMove][y][x] != EmptySquare) {
13742         DisplayMoveError(_("That square is occupied"));
13743         return;
13744     }
13745
13746     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13747 }
13748
13749 void
13750 AcceptEvent()
13751 {
13752     /* Accept a pending offer of any kind from opponent */
13753
13754     if (appData.icsActive) {
13755         SendToICS(ics_prefix);
13756         SendToICS("accept\n");
13757     } else if (cmailMsgLoaded) {
13758         if (currentMove == cmailOldMove &&
13759             commentList[cmailOldMove] != NULL &&
13760             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13761                    "Black offers a draw" : "White offers a draw")) {
13762             TruncateGame();
13763             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13764             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13765         } else {
13766             DisplayError(_("There is no pending offer on this move"), 0);
13767             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13768         }
13769     } else {
13770         /* Not used for offers from chess program */
13771     }
13772 }
13773
13774 void
13775 DeclineEvent()
13776 {
13777     /* Decline a pending offer of any kind from opponent */
13778
13779     if (appData.icsActive) {
13780         SendToICS(ics_prefix);
13781         SendToICS("decline\n");
13782     } else if (cmailMsgLoaded) {
13783         if (currentMove == cmailOldMove &&
13784             commentList[cmailOldMove] != NULL &&
13785             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13786                    "Black offers a draw" : "White offers a draw")) {
13787 #ifdef NOTDEF
13788             AppendComment(cmailOldMove, "Draw declined", TRUE);
13789             DisplayComment(cmailOldMove - 1, "Draw declined");
13790 #endif /*NOTDEF*/
13791         } else {
13792             DisplayError(_("There is no pending offer on this move"), 0);
13793         }
13794     } else {
13795         /* Not used for offers from chess program */
13796     }
13797 }
13798
13799 void
13800 RematchEvent()
13801 {
13802     /* Issue ICS rematch command */
13803     if (appData.icsActive) {
13804         SendToICS(ics_prefix);
13805         SendToICS("rematch\n");
13806     }
13807 }
13808
13809 void
13810 CallFlagEvent()
13811 {
13812     /* Call your opponent's flag (claim a win on time) */
13813     if (appData.icsActive) {
13814         SendToICS(ics_prefix);
13815         SendToICS("flag\n");
13816     } else {
13817         switch (gameMode) {
13818           default:
13819             return;
13820           case MachinePlaysWhite:
13821             if (whiteFlag) {
13822                 if (blackFlag)
13823                   GameEnds(GameIsDrawn, "Both players ran out of time",
13824                            GE_PLAYER);
13825                 else
13826                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13827             } else {
13828                 DisplayError(_("Your opponent is not out of time"), 0);
13829             }
13830             break;
13831           case MachinePlaysBlack:
13832             if (blackFlag) {
13833                 if (whiteFlag)
13834                   GameEnds(GameIsDrawn, "Both players ran out of time",
13835                            GE_PLAYER);
13836                 else
13837                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13838             } else {
13839                 DisplayError(_("Your opponent is not out of time"), 0);
13840             }
13841             break;
13842         }
13843     }
13844 }
13845
13846 void
13847 ClockClick(int which)
13848 {       // [HGM] code moved to back-end from winboard.c
13849         if(which) { // black clock
13850           if (gameMode == EditPosition || gameMode == IcsExamining) {
13851             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13852             SetBlackToPlayEvent();
13853           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13854           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13855           } else if (shiftKey) {
13856             AdjustClock(which, -1);
13857           } else if (gameMode == IcsPlayingWhite ||
13858                      gameMode == MachinePlaysBlack) {
13859             CallFlagEvent();
13860           }
13861         } else { // white clock
13862           if (gameMode == EditPosition || gameMode == IcsExamining) {
13863             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13864             SetWhiteToPlayEvent();
13865           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13866           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13867           } else if (shiftKey) {
13868             AdjustClock(which, -1);
13869           } else if (gameMode == IcsPlayingBlack ||
13870                    gameMode == MachinePlaysWhite) {
13871             CallFlagEvent();
13872           }
13873         }
13874 }
13875
13876 void
13877 DrawEvent()
13878 {
13879     /* Offer draw or accept pending draw offer from opponent */
13880
13881     if (appData.icsActive) {
13882         /* Note: tournament rules require draw offers to be
13883            made after you make your move but before you punch
13884            your clock.  Currently ICS doesn't let you do that;
13885            instead, you immediately punch your clock after making
13886            a move, but you can offer a draw at any time. */
13887
13888         SendToICS(ics_prefix);
13889         SendToICS("draw\n");
13890         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13891     } else if (cmailMsgLoaded) {
13892         if (currentMove == cmailOldMove &&
13893             commentList[cmailOldMove] != NULL &&
13894             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13895                    "Black offers a draw" : "White offers a draw")) {
13896             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13897             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13898         } else if (currentMove == cmailOldMove + 1) {
13899             char *offer = WhiteOnMove(cmailOldMove) ?
13900               "White offers a draw" : "Black offers a draw";
13901             AppendComment(currentMove, offer, TRUE);
13902             DisplayComment(currentMove - 1, offer);
13903             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13904         } else {
13905             DisplayError(_("You must make your move before offering a draw"), 0);
13906             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13907         }
13908     } else if (first.offeredDraw) {
13909         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13910     } else {
13911         if (first.sendDrawOffers) {
13912             SendToProgram("draw\n", &first);
13913             userOfferedDraw = TRUE;
13914         }
13915     }
13916 }
13917
13918 void
13919 AdjournEvent()
13920 {
13921     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13922
13923     if (appData.icsActive) {
13924         SendToICS(ics_prefix);
13925         SendToICS("adjourn\n");
13926     } else {
13927         /* Currently GNU Chess doesn't offer or accept Adjourns */
13928     }
13929 }
13930
13931
13932 void
13933 AbortEvent()
13934 {
13935     /* Offer Abort or accept pending Abort offer from opponent */
13936
13937     if (appData.icsActive) {
13938         SendToICS(ics_prefix);
13939         SendToICS("abort\n");
13940     } else {
13941         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13942     }
13943 }
13944
13945 void
13946 ResignEvent()
13947 {
13948     /* Resign.  You can do this even if it's not your turn. */
13949
13950     if (appData.icsActive) {
13951         SendToICS(ics_prefix);
13952         SendToICS("resign\n");
13953     } else {
13954         switch (gameMode) {
13955           case MachinePlaysWhite:
13956             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13957             break;
13958           case MachinePlaysBlack:
13959             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13960             break;
13961           case EditGame:
13962             if (cmailMsgLoaded) {
13963                 TruncateGame();
13964                 if (WhiteOnMove(cmailOldMove)) {
13965                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13966                 } else {
13967                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13968                 }
13969                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13970             }
13971             break;
13972           default:
13973             break;
13974         }
13975     }
13976 }
13977
13978
13979 void
13980 StopObservingEvent()
13981 {
13982     /* Stop observing current games */
13983     SendToICS(ics_prefix);
13984     SendToICS("unobserve\n");
13985 }
13986
13987 void
13988 StopExaminingEvent()
13989 {
13990     /* Stop observing current game */
13991     SendToICS(ics_prefix);
13992     SendToICS("unexamine\n");
13993 }
13994
13995 void
13996 ForwardInner(target)
13997      int target;
13998 {
13999     int limit;
14000
14001     if (appData.debugMode)
14002         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14003                 target, currentMove, forwardMostMove);
14004
14005     if (gameMode == EditPosition)
14006       return;
14007
14008     if (gameMode == PlayFromGameFile && !pausing)
14009       PauseEvent();
14010
14011     if (gameMode == IcsExamining && pausing)
14012       limit = pauseExamForwardMostMove;
14013     else
14014       limit = forwardMostMove;
14015
14016     if (target > limit) target = limit;
14017
14018     if (target > 0 && moveList[target - 1][0]) {
14019         int fromX, fromY, toX, toY;
14020         toX = moveList[target - 1][2] - AAA;
14021         toY = moveList[target - 1][3] - ONE;
14022         if (moveList[target - 1][1] == '@') {
14023             if (appData.highlightLastMove) {
14024                 SetHighlights(-1, -1, toX, toY);
14025             }
14026         } else {
14027             fromX = moveList[target - 1][0] - AAA;
14028             fromY = moveList[target - 1][1] - ONE;
14029             if (target == currentMove + 1) {
14030                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14031             }
14032             if (appData.highlightLastMove) {
14033                 SetHighlights(fromX, fromY, toX, toY);
14034             }
14035         }
14036     }
14037     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14038         gameMode == Training || gameMode == PlayFromGameFile ||
14039         gameMode == AnalyzeFile) {
14040         while (currentMove < target) {
14041             SendMoveToProgram(currentMove++, &first);
14042         }
14043     } else {
14044         currentMove = target;
14045     }
14046
14047     if (gameMode == EditGame || gameMode == EndOfGame) {
14048         whiteTimeRemaining = timeRemaining[0][currentMove];
14049         blackTimeRemaining = timeRemaining[1][currentMove];
14050     }
14051     DisplayBothClocks();
14052     DisplayMove(currentMove - 1);
14053     DrawPosition(FALSE, boards[currentMove]);
14054     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14055     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14056         DisplayComment(currentMove - 1, commentList[currentMove]);
14057     }
14058     DisplayBook(currentMove);
14059 }
14060
14061
14062 void
14063 ForwardEvent()
14064 {
14065     if (gameMode == IcsExamining && !pausing) {
14066         SendToICS(ics_prefix);
14067         SendToICS("forward\n");
14068     } else {
14069         ForwardInner(currentMove + 1);
14070     }
14071 }
14072
14073 void
14074 ToEndEvent()
14075 {
14076     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14077         /* to optimze, we temporarily turn off analysis mode while we feed
14078          * the remaining moves to the engine. Otherwise we get analysis output
14079          * after each move.
14080          */
14081         if (first.analysisSupport) {
14082           SendToProgram("exit\nforce\n", &first);
14083           first.analyzing = FALSE;
14084         }
14085     }
14086
14087     if (gameMode == IcsExamining && !pausing) {
14088         SendToICS(ics_prefix);
14089         SendToICS("forward 999999\n");
14090     } else {
14091         ForwardInner(forwardMostMove);
14092     }
14093
14094     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14095         /* we have fed all the moves, so reactivate analysis mode */
14096         SendToProgram("analyze\n", &first);
14097         first.analyzing = TRUE;
14098         /*first.maybeThinking = TRUE;*/
14099         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14100     }
14101 }
14102
14103 void
14104 BackwardInner(target)
14105      int target;
14106 {
14107     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14108
14109     if (appData.debugMode)
14110         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14111                 target, currentMove, forwardMostMove);
14112
14113     if (gameMode == EditPosition) return;
14114     if (currentMove <= backwardMostMove) {
14115         ClearHighlights();
14116         DrawPosition(full_redraw, boards[currentMove]);
14117         return;
14118     }
14119     if (gameMode == PlayFromGameFile && !pausing)
14120       PauseEvent();
14121
14122     if (moveList[target][0]) {
14123         int fromX, fromY, toX, toY;
14124         toX = moveList[target][2] - AAA;
14125         toY = moveList[target][3] - ONE;
14126         if (moveList[target][1] == '@') {
14127             if (appData.highlightLastMove) {
14128                 SetHighlights(-1, -1, toX, toY);
14129             }
14130         } else {
14131             fromX = moveList[target][0] - AAA;
14132             fromY = moveList[target][1] - ONE;
14133             if (target == currentMove - 1) {
14134                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14135             }
14136             if (appData.highlightLastMove) {
14137                 SetHighlights(fromX, fromY, toX, toY);
14138             }
14139         }
14140     }
14141     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14142         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14143         while (currentMove > target) {
14144             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14145                 // null move cannot be undone. Reload program with move history before it.
14146                 int i;
14147                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14148                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14149                 }
14150                 SendBoard(&first, i); 
14151                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14152                 break;
14153             }
14154             SendToProgram("undo\n", &first);
14155             currentMove--;
14156         }
14157     } else {
14158         currentMove = target;
14159     }
14160
14161     if (gameMode == EditGame || gameMode == EndOfGame) {
14162         whiteTimeRemaining = timeRemaining[0][currentMove];
14163         blackTimeRemaining = timeRemaining[1][currentMove];
14164     }
14165     DisplayBothClocks();
14166     DisplayMove(currentMove - 1);
14167     DrawPosition(full_redraw, boards[currentMove]);
14168     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14169     // [HGM] PV info: routine tests if comment empty
14170     DisplayComment(currentMove - 1, commentList[currentMove]);
14171     DisplayBook(currentMove);
14172 }
14173
14174 void
14175 BackwardEvent()
14176 {
14177     if (gameMode == IcsExamining && !pausing) {
14178         SendToICS(ics_prefix);
14179         SendToICS("backward\n");
14180     } else {
14181         BackwardInner(currentMove - 1);
14182     }
14183 }
14184
14185 void
14186 ToStartEvent()
14187 {
14188     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14189         /* to optimize, we temporarily turn off analysis mode while we undo
14190          * all the moves. Otherwise we get analysis output after each undo.
14191          */
14192         if (first.analysisSupport) {
14193           SendToProgram("exit\nforce\n", &first);
14194           first.analyzing = FALSE;
14195         }
14196     }
14197
14198     if (gameMode == IcsExamining && !pausing) {
14199         SendToICS(ics_prefix);
14200         SendToICS("backward 999999\n");
14201     } else {
14202         BackwardInner(backwardMostMove);
14203     }
14204
14205     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14206         /* we have fed all the moves, so reactivate analysis mode */
14207         SendToProgram("analyze\n", &first);
14208         first.analyzing = TRUE;
14209         /*first.maybeThinking = TRUE;*/
14210         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14211     }
14212 }
14213
14214 void
14215 ToNrEvent(int to)
14216 {
14217   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14218   if (to >= forwardMostMove) to = forwardMostMove;
14219   if (to <= backwardMostMove) to = backwardMostMove;
14220   if (to < currentMove) {
14221     BackwardInner(to);
14222   } else {
14223     ForwardInner(to);
14224   }
14225 }
14226
14227 void
14228 RevertEvent(Boolean annotate)
14229 {
14230     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14231         return;
14232     }
14233     if (gameMode != IcsExamining) {
14234         DisplayError(_("You are not examining a game"), 0);
14235         return;
14236     }
14237     if (pausing) {
14238         DisplayError(_("You can't revert while pausing"), 0);
14239         return;
14240     }
14241     SendToICS(ics_prefix);
14242     SendToICS("revert\n");
14243 }
14244
14245 void
14246 RetractMoveEvent()
14247 {
14248     switch (gameMode) {
14249       case MachinePlaysWhite:
14250       case MachinePlaysBlack:
14251         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14252             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14253             return;
14254         }
14255         if (forwardMostMove < 2) return;
14256         currentMove = forwardMostMove = forwardMostMove - 2;
14257         whiteTimeRemaining = timeRemaining[0][currentMove];
14258         blackTimeRemaining = timeRemaining[1][currentMove];
14259         DisplayBothClocks();
14260         DisplayMove(currentMove - 1);
14261         ClearHighlights();/*!! could figure this out*/
14262         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14263         SendToProgram("remove\n", &first);
14264         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14265         break;
14266
14267       case BeginningOfGame:
14268       default:
14269         break;
14270
14271       case IcsPlayingWhite:
14272       case IcsPlayingBlack:
14273         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14274             SendToICS(ics_prefix);
14275             SendToICS("takeback 2\n");
14276         } else {
14277             SendToICS(ics_prefix);
14278             SendToICS("takeback 1\n");
14279         }
14280         break;
14281     }
14282 }
14283
14284 void
14285 MoveNowEvent()
14286 {
14287     ChessProgramState *cps;
14288
14289     switch (gameMode) {
14290       case MachinePlaysWhite:
14291         if (!WhiteOnMove(forwardMostMove)) {
14292             DisplayError(_("It is your turn"), 0);
14293             return;
14294         }
14295         cps = &first;
14296         break;
14297       case MachinePlaysBlack:
14298         if (WhiteOnMove(forwardMostMove)) {
14299             DisplayError(_("It is your turn"), 0);
14300             return;
14301         }
14302         cps = &first;
14303         break;
14304       case TwoMachinesPlay:
14305         if (WhiteOnMove(forwardMostMove) ==
14306             (first.twoMachinesColor[0] == 'w')) {
14307             cps = &first;
14308         } else {
14309             cps = &second;
14310         }
14311         break;
14312       case BeginningOfGame:
14313       default:
14314         return;
14315     }
14316     SendToProgram("?\n", cps);
14317 }
14318
14319 void
14320 TruncateGameEvent()
14321 {
14322     EditGameEvent();
14323     if (gameMode != EditGame) return;
14324     TruncateGame();
14325 }
14326
14327 void
14328 TruncateGame()
14329 {
14330     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14331     if (forwardMostMove > currentMove) {
14332         if (gameInfo.resultDetails != NULL) {
14333             free(gameInfo.resultDetails);
14334             gameInfo.resultDetails = NULL;
14335             gameInfo.result = GameUnfinished;
14336         }
14337         forwardMostMove = currentMove;
14338         HistorySet(parseList, backwardMostMove, forwardMostMove,
14339                    currentMove-1);
14340     }
14341 }
14342
14343 void
14344 HintEvent()
14345 {
14346     if (appData.noChessProgram) return;
14347     switch (gameMode) {
14348       case MachinePlaysWhite:
14349         if (WhiteOnMove(forwardMostMove)) {
14350             DisplayError(_("Wait until your turn"), 0);
14351             return;
14352         }
14353         break;
14354       case BeginningOfGame:
14355       case MachinePlaysBlack:
14356         if (!WhiteOnMove(forwardMostMove)) {
14357             DisplayError(_("Wait until your turn"), 0);
14358             return;
14359         }
14360         break;
14361       default:
14362         DisplayError(_("No hint available"), 0);
14363         return;
14364     }
14365     SendToProgram("hint\n", &first);
14366     hintRequested = TRUE;
14367 }
14368
14369 void
14370 BookEvent()
14371 {
14372     if (appData.noChessProgram) return;
14373     switch (gameMode) {
14374       case MachinePlaysWhite:
14375         if (WhiteOnMove(forwardMostMove)) {
14376             DisplayError(_("Wait until your turn"), 0);
14377             return;
14378         }
14379         break;
14380       case BeginningOfGame:
14381       case MachinePlaysBlack:
14382         if (!WhiteOnMove(forwardMostMove)) {
14383             DisplayError(_("Wait until your turn"), 0);
14384             return;
14385         }
14386         break;
14387       case EditPosition:
14388         EditPositionDone(TRUE);
14389         break;
14390       case TwoMachinesPlay:
14391         return;
14392       default:
14393         break;
14394     }
14395     SendToProgram("bk\n", &first);
14396     bookOutput[0] = NULLCHAR;
14397     bookRequested = TRUE;
14398 }
14399
14400 void
14401 AboutGameEvent()
14402 {
14403     char *tags = PGNTags(&gameInfo);
14404     TagsPopUp(tags, CmailMsg());
14405     free(tags);
14406 }
14407
14408 /* end button procedures */
14409
14410 void
14411 PrintPosition(fp, move)
14412      FILE *fp;
14413      int move;
14414 {
14415     int i, j;
14416
14417     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14418         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14419             char c = PieceToChar(boards[move][i][j]);
14420             fputc(c == 'x' ? '.' : c, fp);
14421             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14422         }
14423     }
14424     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14425       fprintf(fp, "white to play\n");
14426     else
14427       fprintf(fp, "black to play\n");
14428 }
14429
14430 void
14431 PrintOpponents(fp)
14432      FILE *fp;
14433 {
14434     if (gameInfo.white != NULL) {
14435         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14436     } else {
14437         fprintf(fp, "\n");
14438     }
14439 }
14440
14441 /* Find last component of program's own name, using some heuristics */
14442 void
14443 TidyProgramName(prog, host, buf)
14444      char *prog, *host, buf[MSG_SIZ];
14445 {
14446     char *p, *q;
14447     int local = (strcmp(host, "localhost") == 0);
14448     while (!local && (p = strchr(prog, ';')) != NULL) {
14449         p++;
14450         while (*p == ' ') p++;
14451         prog = p;
14452     }
14453     if (*prog == '"' || *prog == '\'') {
14454         q = strchr(prog + 1, *prog);
14455     } else {
14456         q = strchr(prog, ' ');
14457     }
14458     if (q == NULL) q = prog + strlen(prog);
14459     p = q;
14460     while (p >= prog && *p != '/' && *p != '\\') p--;
14461     p++;
14462     if(p == prog && *p == '"') p++;
14463     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14464     memcpy(buf, p, q - p);
14465     buf[q - p] = NULLCHAR;
14466     if (!local) {
14467         strcat(buf, "@");
14468         strcat(buf, host);
14469     }
14470 }
14471
14472 char *
14473 TimeControlTagValue()
14474 {
14475     char buf[MSG_SIZ];
14476     if (!appData.clockMode) {
14477       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14478     } else if (movesPerSession > 0) {
14479       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14480     } else if (timeIncrement == 0) {
14481       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14482     } else {
14483       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14484     }
14485     return StrSave(buf);
14486 }
14487
14488 void
14489 SetGameInfo()
14490 {
14491     /* This routine is used only for certain modes */
14492     VariantClass v = gameInfo.variant;
14493     ChessMove r = GameUnfinished;
14494     char *p = NULL;
14495
14496     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14497         r = gameInfo.result;
14498         p = gameInfo.resultDetails;
14499         gameInfo.resultDetails = NULL;
14500     }
14501     ClearGameInfo(&gameInfo);
14502     gameInfo.variant = v;
14503
14504     switch (gameMode) {
14505       case MachinePlaysWhite:
14506         gameInfo.event = StrSave( appData.pgnEventHeader );
14507         gameInfo.site = StrSave(HostName());
14508         gameInfo.date = PGNDate();
14509         gameInfo.round = StrSave("-");
14510         gameInfo.white = StrSave(first.tidy);
14511         gameInfo.black = StrSave(UserName());
14512         gameInfo.timeControl = TimeControlTagValue();
14513         break;
14514
14515       case MachinePlaysBlack:
14516         gameInfo.event = StrSave( appData.pgnEventHeader );
14517         gameInfo.site = StrSave(HostName());
14518         gameInfo.date = PGNDate();
14519         gameInfo.round = StrSave("-");
14520         gameInfo.white = StrSave(UserName());
14521         gameInfo.black = StrSave(first.tidy);
14522         gameInfo.timeControl = TimeControlTagValue();
14523         break;
14524
14525       case TwoMachinesPlay:
14526         gameInfo.event = StrSave( appData.pgnEventHeader );
14527         gameInfo.site = StrSave(HostName());
14528         gameInfo.date = PGNDate();
14529         if (roundNr > 0) {
14530             char buf[MSG_SIZ];
14531             snprintf(buf, MSG_SIZ, "%d", roundNr);
14532             gameInfo.round = StrSave(buf);
14533         } else {
14534             gameInfo.round = StrSave("-");
14535         }
14536         if (first.twoMachinesColor[0] == 'w') {
14537             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14538             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14539         } else {
14540             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14541             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14542         }
14543         gameInfo.timeControl = TimeControlTagValue();
14544         break;
14545
14546       case EditGame:
14547         gameInfo.event = StrSave("Edited game");
14548         gameInfo.site = StrSave(HostName());
14549         gameInfo.date = PGNDate();
14550         gameInfo.round = StrSave("-");
14551         gameInfo.white = StrSave("-");
14552         gameInfo.black = StrSave("-");
14553         gameInfo.result = r;
14554         gameInfo.resultDetails = p;
14555         break;
14556
14557       case EditPosition:
14558         gameInfo.event = StrSave("Edited position");
14559         gameInfo.site = StrSave(HostName());
14560         gameInfo.date = PGNDate();
14561         gameInfo.round = StrSave("-");
14562         gameInfo.white = StrSave("-");
14563         gameInfo.black = StrSave("-");
14564         break;
14565
14566       case IcsPlayingWhite:
14567       case IcsPlayingBlack:
14568       case IcsObserving:
14569       case IcsExamining:
14570         break;
14571
14572       case PlayFromGameFile:
14573         gameInfo.event = StrSave("Game from non-PGN file");
14574         gameInfo.site = StrSave(HostName());
14575         gameInfo.date = PGNDate();
14576         gameInfo.round = StrSave("-");
14577         gameInfo.white = StrSave("?");
14578         gameInfo.black = StrSave("?");
14579         break;
14580
14581       default:
14582         break;
14583     }
14584 }
14585
14586 void
14587 ReplaceComment(index, text)
14588      int index;
14589      char *text;
14590 {
14591     int len;
14592     char *p;
14593     float score;
14594
14595     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14596        pvInfoList[index-1].depth == len &&
14597        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14598        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14599     while (*text == '\n') text++;
14600     len = strlen(text);
14601     while (len > 0 && text[len - 1] == '\n') len--;
14602
14603     if (commentList[index] != NULL)
14604       free(commentList[index]);
14605
14606     if (len == 0) {
14607         commentList[index] = NULL;
14608         return;
14609     }
14610   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14611       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14612       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14613     commentList[index] = (char *) malloc(len + 2);
14614     strncpy(commentList[index], text, len);
14615     commentList[index][len] = '\n';
14616     commentList[index][len + 1] = NULLCHAR;
14617   } else {
14618     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14619     char *p;
14620     commentList[index] = (char *) malloc(len + 7);
14621     safeStrCpy(commentList[index], "{\n", 3);
14622     safeStrCpy(commentList[index]+2, text, len+1);
14623     commentList[index][len+2] = NULLCHAR;
14624     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14625     strcat(commentList[index], "\n}\n");
14626   }
14627 }
14628
14629 void
14630 CrushCRs(text)
14631      char *text;
14632 {
14633   char *p = text;
14634   char *q = text;
14635   char ch;
14636
14637   do {
14638     ch = *p++;
14639     if (ch == '\r') continue;
14640     *q++ = ch;
14641   } while (ch != '\0');
14642 }
14643
14644 void
14645 AppendComment(index, text, addBraces)
14646      int index;
14647      char *text;
14648      Boolean addBraces; // [HGM] braces: tells if we should add {}
14649 {
14650     int oldlen, len;
14651     char *old;
14652
14653 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14654     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14655
14656     CrushCRs(text);
14657     while (*text == '\n') text++;
14658     len = strlen(text);
14659     while (len > 0 && text[len - 1] == '\n') len--;
14660
14661     if (len == 0) return;
14662
14663     if (commentList[index] != NULL) {
14664       Boolean addClosingBrace = addBraces;
14665         old = commentList[index];
14666         oldlen = strlen(old);
14667         while(commentList[index][oldlen-1] ==  '\n')
14668           commentList[index][--oldlen] = NULLCHAR;
14669         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14670         safeStrCpy(commentList[index], old, oldlen + len + 6);
14671         free(old);
14672         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14673         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14674           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14675           while (*text == '\n') { text++; len--; }
14676           commentList[index][--oldlen] = NULLCHAR;
14677       }
14678         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14679         else          strcat(commentList[index], "\n");
14680         strcat(commentList[index], text);
14681         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14682         else          strcat(commentList[index], "\n");
14683     } else {
14684         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14685         if(addBraces)
14686           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14687         else commentList[index][0] = NULLCHAR;
14688         strcat(commentList[index], text);
14689         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14690         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14691     }
14692 }
14693
14694 static char * FindStr( char * text, char * sub_text )
14695 {
14696     char * result = strstr( text, sub_text );
14697
14698     if( result != NULL ) {
14699         result += strlen( sub_text );
14700     }
14701
14702     return result;
14703 }
14704
14705 /* [AS] Try to extract PV info from PGN comment */
14706 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14707 char *GetInfoFromComment( int index, char * text )
14708 {
14709     char * sep = text, *p;
14710
14711     if( text != NULL && index > 0 ) {
14712         int score = 0;
14713         int depth = 0;
14714         int time = -1, sec = 0, deci;
14715         char * s_eval = FindStr( text, "[%eval " );
14716         char * s_emt = FindStr( text, "[%emt " );
14717
14718         if( s_eval != NULL || s_emt != NULL ) {
14719             /* New style */
14720             char delim;
14721
14722             if( s_eval != NULL ) {
14723                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14724                     return text;
14725                 }
14726
14727                 if( delim != ']' ) {
14728                     return text;
14729                 }
14730             }
14731
14732             if( s_emt != NULL ) {
14733             }
14734                 return text;
14735         }
14736         else {
14737             /* We expect something like: [+|-]nnn.nn/dd */
14738             int score_lo = 0;
14739
14740             if(*text != '{') return text; // [HGM] braces: must be normal comment
14741
14742             sep = strchr( text, '/' );
14743             if( sep == NULL || sep < (text+4) ) {
14744                 return text;
14745             }
14746
14747             p = text;
14748             if(p[1] == '(') { // comment starts with PV
14749                p = strchr(p, ')'); // locate end of PV
14750                if(p == NULL || sep < p+5) return text;
14751                // at this point we have something like "{(.*) +0.23/6 ..."
14752                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14753                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14754                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14755             }
14756             time = -1; sec = -1; deci = -1;
14757             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14758                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14759                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14760                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14761                 return text;
14762             }
14763
14764             if( score_lo < 0 || score_lo >= 100 ) {
14765                 return text;
14766             }
14767
14768             if(sec >= 0) time = 600*time + 10*sec; else
14769             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14770
14771             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14772
14773             /* [HGM] PV time: now locate end of PV info */
14774             while( *++sep >= '0' && *sep <= '9'); // strip depth
14775             if(time >= 0)
14776             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14777             if(sec >= 0)
14778             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14779             if(deci >= 0)
14780             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14781             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14782         }
14783
14784         if( depth <= 0 ) {
14785             return text;
14786         }
14787
14788         if( time < 0 ) {
14789             time = -1;
14790         }
14791
14792         pvInfoList[index-1].depth = depth;
14793         pvInfoList[index-1].score = score;
14794         pvInfoList[index-1].time  = 10*time; // centi-sec
14795         if(*sep == '}') *sep = 0; else *--sep = '{';
14796         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14797     }
14798     return sep;
14799 }
14800
14801 void
14802 SendToProgram(message, cps)
14803      char *message;
14804      ChessProgramState *cps;
14805 {
14806     int count, outCount, error;
14807     char buf[MSG_SIZ];
14808
14809     if (cps->pr == NULL) return;
14810     Attention(cps);
14811
14812     if (appData.debugMode) {
14813         TimeMark now;
14814         GetTimeMark(&now);
14815         fprintf(debugFP, "%ld >%-6s: %s",
14816                 SubtractTimeMarks(&now, &programStartTime),
14817                 cps->which, message);
14818     }
14819
14820     count = strlen(message);
14821     outCount = OutputToProcess(cps->pr, message, count, &error);
14822     if (outCount < count && !exiting
14823                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14824       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14825       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14826         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14827             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14828                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14829                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14830                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14831             } else {
14832                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14833                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14834                 gameInfo.result = res;
14835             }
14836             gameInfo.resultDetails = StrSave(buf);
14837         }
14838         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14839         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14840     }
14841 }
14842
14843 void
14844 ReceiveFromProgram(isr, closure, message, count, error)
14845      InputSourceRef isr;
14846      VOIDSTAR closure;
14847      char *message;
14848      int count;
14849      int error;
14850 {
14851     char *end_str;
14852     char buf[MSG_SIZ];
14853     ChessProgramState *cps = (ChessProgramState *)closure;
14854
14855     if (isr != cps->isr) return; /* Killed intentionally */
14856     if (count <= 0) {
14857         if (count == 0) {
14858             RemoveInputSource(cps->isr);
14859             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14860             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14861                     _(cps->which), cps->program);
14862         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14863                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14864                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14865                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14866                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14867                 } else {
14868                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14869                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14870                     gameInfo.result = res;
14871                 }
14872                 gameInfo.resultDetails = StrSave(buf);
14873             }
14874             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14875             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14876         } else {
14877             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14878                     _(cps->which), cps->program);
14879             RemoveInputSource(cps->isr);
14880
14881             /* [AS] Program is misbehaving badly... kill it */
14882             if( count == -2 ) {
14883                 DestroyChildProcess( cps->pr, 9 );
14884                 cps->pr = NoProc;
14885             }
14886
14887             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14888         }
14889         return;
14890     }
14891
14892     if ((end_str = strchr(message, '\r')) != NULL)
14893       *end_str = NULLCHAR;
14894     if ((end_str = strchr(message, '\n')) != NULL)
14895       *end_str = NULLCHAR;
14896
14897     if (appData.debugMode) {
14898         TimeMark now; int print = 1;
14899         char *quote = ""; char c; int i;
14900
14901         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14902                 char start = message[0];
14903                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14904                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14905                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14906                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14907                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14908                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14909                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14910                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14911                    sscanf(message, "hint: %c", &c)!=1 && 
14912                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14913                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14914                     print = (appData.engineComments >= 2);
14915                 }
14916                 message[0] = start; // restore original message
14917         }
14918         if(print) {
14919                 GetTimeMark(&now);
14920                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14921                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14922                         quote,
14923                         message);
14924         }
14925     }
14926
14927     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14928     if (appData.icsEngineAnalyze) {
14929         if (strstr(message, "whisper") != NULL ||
14930              strstr(message, "kibitz") != NULL ||
14931             strstr(message, "tellics") != NULL) return;
14932     }
14933
14934     HandleMachineMove(message, cps);
14935 }
14936
14937
14938 void
14939 SendTimeControl(cps, mps, tc, inc, sd, st)
14940      ChessProgramState *cps;
14941      int mps, inc, sd, st;
14942      long tc;
14943 {
14944     char buf[MSG_SIZ];
14945     int seconds;
14946
14947     if( timeControl_2 > 0 ) {
14948         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14949             tc = timeControl_2;
14950         }
14951     }
14952     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14953     inc /= cps->timeOdds;
14954     st  /= cps->timeOdds;
14955
14956     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14957
14958     if (st > 0) {
14959       /* Set exact time per move, normally using st command */
14960       if (cps->stKludge) {
14961         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14962         seconds = st % 60;
14963         if (seconds == 0) {
14964           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14965         } else {
14966           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14967         }
14968       } else {
14969         snprintf(buf, MSG_SIZ, "st %d\n", st);
14970       }
14971     } else {
14972       /* Set conventional or incremental time control, using level command */
14973       if (seconds == 0) {
14974         /* Note old gnuchess bug -- minutes:seconds used to not work.
14975            Fixed in later versions, but still avoid :seconds
14976            when seconds is 0. */
14977         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14978       } else {
14979         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14980                  seconds, inc/1000.);
14981       }
14982     }
14983     SendToProgram(buf, cps);
14984
14985     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14986     /* Orthogonally, limit search to given depth */
14987     if (sd > 0) {
14988       if (cps->sdKludge) {
14989         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14990       } else {
14991         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14992       }
14993       SendToProgram(buf, cps);
14994     }
14995
14996     if(cps->nps >= 0) { /* [HGM] nps */
14997         if(cps->supportsNPS == FALSE)
14998           cps->nps = -1; // don't use if engine explicitly says not supported!
14999         else {
15000           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15001           SendToProgram(buf, cps);
15002         }
15003     }
15004 }
15005
15006 ChessProgramState *WhitePlayer()
15007 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15008 {
15009     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15010        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15011         return &second;
15012     return &first;
15013 }
15014
15015 void
15016 SendTimeRemaining(cps, machineWhite)
15017      ChessProgramState *cps;
15018      int /*boolean*/ machineWhite;
15019 {
15020     char message[MSG_SIZ];
15021     long time, otime;
15022
15023     /* Note: this routine must be called when the clocks are stopped
15024        or when they have *just* been set or switched; otherwise
15025        it will be off by the time since the current tick started.
15026     */
15027     if (machineWhite) {
15028         time = whiteTimeRemaining / 10;
15029         otime = blackTimeRemaining / 10;
15030     } else {
15031         time = blackTimeRemaining / 10;
15032         otime = whiteTimeRemaining / 10;
15033     }
15034     /* [HGM] translate opponent's time by time-odds factor */
15035     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15036     if (appData.debugMode) {
15037         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15038     }
15039
15040     if (time <= 0) time = 1;
15041     if (otime <= 0) otime = 1;
15042
15043     snprintf(message, MSG_SIZ, "time %ld\n", time);
15044     SendToProgram(message, cps);
15045
15046     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15047     SendToProgram(message, cps);
15048 }
15049
15050 int
15051 BoolFeature(p, name, loc, cps)
15052      char **p;
15053      char *name;
15054      int *loc;
15055      ChessProgramState *cps;
15056 {
15057   char buf[MSG_SIZ];
15058   int len = strlen(name);
15059   int val;
15060
15061   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15062     (*p) += len + 1;
15063     sscanf(*p, "%d", &val);
15064     *loc = (val != 0);
15065     while (**p && **p != ' ')
15066       (*p)++;
15067     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15068     SendToProgram(buf, cps);
15069     return TRUE;
15070   }
15071   return FALSE;
15072 }
15073
15074 int
15075 IntFeature(p, name, loc, cps)
15076      char **p;
15077      char *name;
15078      int *loc;
15079      ChessProgramState *cps;
15080 {
15081   char buf[MSG_SIZ];
15082   int len = strlen(name);
15083   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15084     (*p) += len + 1;
15085     sscanf(*p, "%d", loc);
15086     while (**p && **p != ' ') (*p)++;
15087     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15088     SendToProgram(buf, cps);
15089     return TRUE;
15090   }
15091   return FALSE;
15092 }
15093
15094 int
15095 StringFeature(p, name, loc, cps)
15096      char **p;
15097      char *name;
15098      char loc[];
15099      ChessProgramState *cps;
15100 {
15101   char buf[MSG_SIZ];
15102   int len = strlen(name);
15103   if (strncmp((*p), name, len) == 0
15104       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15105     (*p) += len + 2;
15106     sscanf(*p, "%[^\"]", loc);
15107     while (**p && **p != '\"') (*p)++;
15108     if (**p == '\"') (*p)++;
15109     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15110     SendToProgram(buf, cps);
15111     return TRUE;
15112   }
15113   return FALSE;
15114 }
15115
15116 int
15117 ParseOption(Option *opt, ChessProgramState *cps)
15118 // [HGM] options: process the string that defines an engine option, and determine
15119 // name, type, default value, and allowed value range
15120 {
15121         char *p, *q, buf[MSG_SIZ];
15122         int n, min = (-1)<<31, max = 1<<31, def;
15123
15124         if(p = strstr(opt->name, " -spin ")) {
15125             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15126             if(max < min) max = min; // enforce consistency
15127             if(def < min) def = min;
15128             if(def > max) def = max;
15129             opt->value = def;
15130             opt->min = min;
15131             opt->max = max;
15132             opt->type = Spin;
15133         } else if((p = strstr(opt->name, " -slider "))) {
15134             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15135             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15136             if(max < min) max = min; // enforce consistency
15137             if(def < min) def = min;
15138             if(def > max) def = max;
15139             opt->value = def;
15140             opt->min = min;
15141             opt->max = max;
15142             opt->type = Spin; // Slider;
15143         } else if((p = strstr(opt->name, " -string "))) {
15144             opt->textValue = p+9;
15145             opt->type = TextBox;
15146         } else if((p = strstr(opt->name, " -file "))) {
15147             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15148             opt->textValue = p+7;
15149             opt->type = FileName; // FileName;
15150         } else if((p = strstr(opt->name, " -path "))) {
15151             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15152             opt->textValue = p+7;
15153             opt->type = PathName; // PathName;
15154         } else if(p = strstr(opt->name, " -check ")) {
15155             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15156             opt->value = (def != 0);
15157             opt->type = CheckBox;
15158         } else if(p = strstr(opt->name, " -combo ")) {
15159             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15160             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15161             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15162             opt->value = n = 0;
15163             while(q = StrStr(q, " /// ")) {
15164                 n++; *q = 0;    // count choices, and null-terminate each of them
15165                 q += 5;
15166                 if(*q == '*') { // remember default, which is marked with * prefix
15167                     q++;
15168                     opt->value = n;
15169                 }
15170                 cps->comboList[cps->comboCnt++] = q;
15171             }
15172             cps->comboList[cps->comboCnt++] = NULL;
15173             opt->max = n + 1;
15174             opt->type = ComboBox;
15175         } else if(p = strstr(opt->name, " -button")) {
15176             opt->type = Button;
15177         } else if(p = strstr(opt->name, " -save")) {
15178             opt->type = SaveButton;
15179         } else return FALSE;
15180         *p = 0; // terminate option name
15181         // now look if the command-line options define a setting for this engine option.
15182         if(cps->optionSettings && cps->optionSettings[0])
15183             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15184         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15185           snprintf(buf, MSG_SIZ, "option %s", p);
15186                 if(p = strstr(buf, ",")) *p = 0;
15187                 if(q = strchr(buf, '=')) switch(opt->type) {
15188                     case ComboBox:
15189                         for(n=0; n<opt->max; n++)
15190                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15191                         break;
15192                     case TextBox:
15193                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15194                         break;
15195                     case Spin:
15196                     case CheckBox:
15197                         opt->value = atoi(q+1);
15198                     default:
15199                         break;
15200                 }
15201                 strcat(buf, "\n");
15202                 SendToProgram(buf, cps);
15203         }
15204         return TRUE;
15205 }
15206
15207 void
15208 FeatureDone(cps, val)
15209      ChessProgramState* cps;
15210      int val;
15211 {
15212   DelayedEventCallback cb = GetDelayedEvent();
15213   if ((cb == InitBackEnd3 && cps == &first) ||
15214       (cb == SettingsMenuIfReady && cps == &second) ||
15215       (cb == LoadEngine) ||
15216       (cb == TwoMachinesEventIfReady)) {
15217     CancelDelayedEvent();
15218     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15219   }
15220   cps->initDone = val;
15221 }
15222
15223 /* Parse feature command from engine */
15224 void
15225 ParseFeatures(args, cps)
15226      char* args;
15227      ChessProgramState *cps;
15228 {
15229   char *p = args;
15230   char *q;
15231   int val;
15232   char buf[MSG_SIZ];
15233
15234   for (;;) {
15235     while (*p == ' ') p++;
15236     if (*p == NULLCHAR) return;
15237
15238     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15239     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15240     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15241     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15242     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15243     if (BoolFeature(&p, "reuse", &val, cps)) {
15244       /* Engine can disable reuse, but can't enable it if user said no */
15245       if (!val) cps->reuse = FALSE;
15246       continue;
15247     }
15248     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15249     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15250       if (gameMode == TwoMachinesPlay) {
15251         DisplayTwoMachinesTitle();
15252       } else {
15253         DisplayTitle("");
15254       }
15255       continue;
15256     }
15257     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15258     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15259     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15260     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15261     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15262     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15263     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15264     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15265     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15266     if (IntFeature(&p, "done", &val, cps)) {
15267       FeatureDone(cps, val);
15268       continue;
15269     }
15270     /* Added by Tord: */
15271     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15272     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15273     /* End of additions by Tord */
15274
15275     /* [HGM] added features: */
15276     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15277     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15278     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15279     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15280     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15281     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15282     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15283         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15284           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15285             SendToProgram(buf, cps);
15286             continue;
15287         }
15288         if(cps->nrOptions >= MAX_OPTIONS) {
15289             cps->nrOptions--;
15290             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15291             DisplayError(buf, 0);
15292         }
15293         continue;
15294     }
15295     /* End of additions by HGM */
15296
15297     /* unknown feature: complain and skip */
15298     q = p;
15299     while (*q && *q != '=') q++;
15300     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15301     SendToProgram(buf, cps);
15302     p = q;
15303     if (*p == '=') {
15304       p++;
15305       if (*p == '\"') {
15306         p++;
15307         while (*p && *p != '\"') p++;
15308         if (*p == '\"') p++;
15309       } else {
15310         while (*p && *p != ' ') p++;
15311       }
15312     }
15313   }
15314
15315 }
15316
15317 void
15318 PeriodicUpdatesEvent(newState)
15319      int newState;
15320 {
15321     if (newState == appData.periodicUpdates)
15322       return;
15323
15324     appData.periodicUpdates=newState;
15325
15326     /* Display type changes, so update it now */
15327 //    DisplayAnalysis();
15328
15329     /* Get the ball rolling again... */
15330     if (newState) {
15331         AnalysisPeriodicEvent(1);
15332         StartAnalysisClock();
15333     }
15334 }
15335
15336 void
15337 PonderNextMoveEvent(newState)
15338      int newState;
15339 {
15340     if (newState == appData.ponderNextMove) return;
15341     if (gameMode == EditPosition) EditPositionDone(TRUE);
15342     if (newState) {
15343         SendToProgram("hard\n", &first);
15344         if (gameMode == TwoMachinesPlay) {
15345             SendToProgram("hard\n", &second);
15346         }
15347     } else {
15348         SendToProgram("easy\n", &first);
15349         thinkOutput[0] = NULLCHAR;
15350         if (gameMode == TwoMachinesPlay) {
15351             SendToProgram("easy\n", &second);
15352         }
15353     }
15354     appData.ponderNextMove = newState;
15355 }
15356
15357 void
15358 NewSettingEvent(option, feature, command, value)
15359      char *command;
15360      int option, value, *feature;
15361 {
15362     char buf[MSG_SIZ];
15363
15364     if (gameMode == EditPosition) EditPositionDone(TRUE);
15365     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15366     if(feature == NULL || *feature) SendToProgram(buf, &first);
15367     if (gameMode == TwoMachinesPlay) {
15368         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15369     }
15370 }
15371
15372 void
15373 ShowThinkingEvent()
15374 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15375 {
15376     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15377     int newState = appData.showThinking
15378         // [HGM] thinking: other features now need thinking output as well
15379         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15380
15381     if (oldState == newState) return;
15382     oldState = newState;
15383     if (gameMode == EditPosition) EditPositionDone(TRUE);
15384     if (oldState) {
15385         SendToProgram("post\n", &first);
15386         if (gameMode == TwoMachinesPlay) {
15387             SendToProgram("post\n", &second);
15388         }
15389     } else {
15390         SendToProgram("nopost\n", &first);
15391         thinkOutput[0] = NULLCHAR;
15392         if (gameMode == TwoMachinesPlay) {
15393             SendToProgram("nopost\n", &second);
15394         }
15395     }
15396 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15397 }
15398
15399 void
15400 AskQuestionEvent(title, question, replyPrefix, which)
15401      char *title; char *question; char *replyPrefix; char *which;
15402 {
15403   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15404   if (pr == NoProc) return;
15405   AskQuestion(title, question, replyPrefix, pr);
15406 }
15407
15408 void
15409 TypeInEvent(char firstChar)
15410 {
15411     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15412         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15413         gameMode == AnalyzeMode || gameMode == EditGame || 
15414         gameMode == EditPosition || gameMode == IcsExamining ||
15415         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15416         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15417                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15418                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15419         gameMode == Training) PopUpMoveDialog(firstChar);
15420 }
15421
15422 void
15423 TypeInDoneEvent(char *move)
15424 {
15425         Board board;
15426         int n, fromX, fromY, toX, toY;
15427         char promoChar;
15428         ChessMove moveType;
15429
15430         // [HGM] FENedit
15431         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15432                 EditPositionPasteFEN(move);
15433                 return;
15434         }
15435         // [HGM] movenum: allow move number to be typed in any mode
15436         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15437           ToNrEvent(2*n-1);
15438           return;
15439         }
15440
15441       if (gameMode != EditGame && currentMove != forwardMostMove && 
15442         gameMode != Training) {
15443         DisplayMoveError(_("Displayed move is not current"));
15444       } else {
15445         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15446           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15447         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15448         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15449           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15450           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15451         } else {
15452           DisplayMoveError(_("Could not parse move"));
15453         }
15454       }
15455 }
15456
15457 void
15458 DisplayMove(moveNumber)
15459      int moveNumber;
15460 {
15461     char message[MSG_SIZ];
15462     char res[MSG_SIZ];
15463     char cpThinkOutput[MSG_SIZ];
15464
15465     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15466
15467     if (moveNumber == forwardMostMove - 1 ||
15468         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15469
15470         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15471
15472         if (strchr(cpThinkOutput, '\n')) {
15473             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15474         }
15475     } else {
15476         *cpThinkOutput = NULLCHAR;
15477     }
15478
15479     /* [AS] Hide thinking from human user */
15480     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15481         *cpThinkOutput = NULLCHAR;
15482         if( thinkOutput[0] != NULLCHAR ) {
15483             int i;
15484
15485             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15486                 cpThinkOutput[i] = '.';
15487             }
15488             cpThinkOutput[i] = NULLCHAR;
15489             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15490         }
15491     }
15492
15493     if (moveNumber == forwardMostMove - 1 &&
15494         gameInfo.resultDetails != NULL) {
15495         if (gameInfo.resultDetails[0] == NULLCHAR) {
15496           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15497         } else {
15498           snprintf(res, MSG_SIZ, " {%s} %s",
15499                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15500         }
15501     } else {
15502         res[0] = NULLCHAR;
15503     }
15504
15505     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15506         DisplayMessage(res, cpThinkOutput);
15507     } else {
15508       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15509                 WhiteOnMove(moveNumber) ? " " : ".. ",
15510                 parseList[moveNumber], res);
15511         DisplayMessage(message, cpThinkOutput);
15512     }
15513 }
15514
15515 void
15516 DisplayComment(moveNumber, text)
15517      int moveNumber;
15518      char *text;
15519 {
15520     char title[MSG_SIZ];
15521
15522     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15523       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15524     } else {
15525       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15526               WhiteOnMove(moveNumber) ? " " : ".. ",
15527               parseList[moveNumber]);
15528     }
15529     if (text != NULL && (appData.autoDisplayComment || commentUp))
15530         CommentPopUp(title, text);
15531 }
15532
15533 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15534  * might be busy thinking or pondering.  It can be omitted if your
15535  * gnuchess is configured to stop thinking immediately on any user
15536  * input.  However, that gnuchess feature depends on the FIONREAD
15537  * ioctl, which does not work properly on some flavors of Unix.
15538  */
15539 void
15540 Attention(cps)
15541      ChessProgramState *cps;
15542 {
15543 #if ATTENTION
15544     if (!cps->useSigint) return;
15545     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15546     switch (gameMode) {
15547       case MachinePlaysWhite:
15548       case MachinePlaysBlack:
15549       case TwoMachinesPlay:
15550       case IcsPlayingWhite:
15551       case IcsPlayingBlack:
15552       case AnalyzeMode:
15553       case AnalyzeFile:
15554         /* Skip if we know it isn't thinking */
15555         if (!cps->maybeThinking) return;
15556         if (appData.debugMode)
15557           fprintf(debugFP, "Interrupting %s\n", cps->which);
15558         InterruptChildProcess(cps->pr);
15559         cps->maybeThinking = FALSE;
15560         break;
15561       default:
15562         break;
15563     }
15564 #endif /*ATTENTION*/
15565 }
15566
15567 int
15568 CheckFlags()
15569 {
15570     if (whiteTimeRemaining <= 0) {
15571         if (!whiteFlag) {
15572             whiteFlag = TRUE;
15573             if (appData.icsActive) {
15574                 if (appData.autoCallFlag &&
15575                     gameMode == IcsPlayingBlack && !blackFlag) {
15576                   SendToICS(ics_prefix);
15577                   SendToICS("flag\n");
15578                 }
15579             } else {
15580                 if (blackFlag) {
15581                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15582                 } else {
15583                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15584                     if (appData.autoCallFlag) {
15585                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15586                         return TRUE;
15587                     }
15588                 }
15589             }
15590         }
15591     }
15592     if (blackTimeRemaining <= 0) {
15593         if (!blackFlag) {
15594             blackFlag = TRUE;
15595             if (appData.icsActive) {
15596                 if (appData.autoCallFlag &&
15597                     gameMode == IcsPlayingWhite && !whiteFlag) {
15598                   SendToICS(ics_prefix);
15599                   SendToICS("flag\n");
15600                 }
15601             } else {
15602                 if (whiteFlag) {
15603                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15604                 } else {
15605                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15606                     if (appData.autoCallFlag) {
15607                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15608                         return TRUE;
15609                     }
15610                 }
15611             }
15612         }
15613     }
15614     return FALSE;
15615 }
15616
15617 void
15618 CheckTimeControl()
15619 {
15620     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15621         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15622
15623     /*
15624      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15625      */
15626     if ( !WhiteOnMove(forwardMostMove) ) {
15627         /* White made time control */
15628         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15629         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15630         /* [HGM] time odds: correct new time quota for time odds! */
15631                                             / WhitePlayer()->timeOdds;
15632         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15633     } else {
15634         lastBlack -= blackTimeRemaining;
15635         /* Black made time control */
15636         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15637                                             / WhitePlayer()->other->timeOdds;
15638         lastWhite = whiteTimeRemaining;
15639     }
15640 }
15641
15642 void
15643 DisplayBothClocks()
15644 {
15645     int wom = gameMode == EditPosition ?
15646       !blackPlaysFirst : WhiteOnMove(currentMove);
15647     DisplayWhiteClock(whiteTimeRemaining, wom);
15648     DisplayBlackClock(blackTimeRemaining, !wom);
15649 }
15650
15651
15652 /* Timekeeping seems to be a portability nightmare.  I think everyone
15653    has ftime(), but I'm really not sure, so I'm including some ifdefs
15654    to use other calls if you don't.  Clocks will be less accurate if
15655    you have neither ftime nor gettimeofday.
15656 */
15657
15658 /* VS 2008 requires the #include outside of the function */
15659 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15660 #include <sys/timeb.h>
15661 #endif
15662
15663 /* Get the current time as a TimeMark */
15664 void
15665 GetTimeMark(tm)
15666      TimeMark *tm;
15667 {
15668 #if HAVE_GETTIMEOFDAY
15669
15670     struct timeval timeVal;
15671     struct timezone timeZone;
15672
15673     gettimeofday(&timeVal, &timeZone);
15674     tm->sec = (long) timeVal.tv_sec;
15675     tm->ms = (int) (timeVal.tv_usec / 1000L);
15676
15677 #else /*!HAVE_GETTIMEOFDAY*/
15678 #if HAVE_FTIME
15679
15680 // include <sys/timeb.h> / moved to just above start of function
15681     struct timeb timeB;
15682
15683     ftime(&timeB);
15684     tm->sec = (long) timeB.time;
15685     tm->ms = (int) timeB.millitm;
15686
15687 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15688     tm->sec = (long) time(NULL);
15689     tm->ms = 0;
15690 #endif
15691 #endif
15692 }
15693
15694 /* Return the difference in milliseconds between two
15695    time marks.  We assume the difference will fit in a long!
15696 */
15697 long
15698 SubtractTimeMarks(tm2, tm1)
15699      TimeMark *tm2, *tm1;
15700 {
15701     return 1000L*(tm2->sec - tm1->sec) +
15702            (long) (tm2->ms - tm1->ms);
15703 }
15704
15705
15706 /*
15707  * Code to manage the game clocks.
15708  *
15709  * In tournament play, black starts the clock and then white makes a move.
15710  * We give the human user a slight advantage if he is playing white---the
15711  * clocks don't run until he makes his first move, so it takes zero time.
15712  * Also, we don't account for network lag, so we could get out of sync
15713  * with GNU Chess's clock -- but then, referees are always right.
15714  */
15715
15716 static TimeMark tickStartTM;
15717 static long intendedTickLength;
15718
15719 long
15720 NextTickLength(timeRemaining)
15721      long timeRemaining;
15722 {
15723     long nominalTickLength, nextTickLength;
15724
15725     if (timeRemaining > 0L && timeRemaining <= 10000L)
15726       nominalTickLength = 100L;
15727     else
15728       nominalTickLength = 1000L;
15729     nextTickLength = timeRemaining % nominalTickLength;
15730     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15731
15732     return nextTickLength;
15733 }
15734
15735 /* Adjust clock one minute up or down */
15736 void
15737 AdjustClock(Boolean which, int dir)
15738 {
15739     if(which) blackTimeRemaining += 60000*dir;
15740     else      whiteTimeRemaining += 60000*dir;
15741     DisplayBothClocks();
15742 }
15743
15744 /* Stop clocks and reset to a fresh time control */
15745 void
15746 ResetClocks()
15747 {
15748     (void) StopClockTimer();
15749     if (appData.icsActive) {
15750         whiteTimeRemaining = blackTimeRemaining = 0;
15751     } else if (searchTime) {
15752         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15753         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15754     } else { /* [HGM] correct new time quote for time odds */
15755         whiteTC = blackTC = fullTimeControlString;
15756         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15757         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15758     }
15759     if (whiteFlag || blackFlag) {
15760         DisplayTitle("");
15761         whiteFlag = blackFlag = FALSE;
15762     }
15763     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15764     DisplayBothClocks();
15765 }
15766
15767 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15768
15769 /* Decrement running clock by amount of time that has passed */
15770 void
15771 DecrementClocks()
15772 {
15773     long timeRemaining;
15774     long lastTickLength, fudge;
15775     TimeMark now;
15776
15777     if (!appData.clockMode) return;
15778     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15779
15780     GetTimeMark(&now);
15781
15782     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15783
15784     /* Fudge if we woke up a little too soon */
15785     fudge = intendedTickLength - lastTickLength;
15786     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15787
15788     if (WhiteOnMove(forwardMostMove)) {
15789         if(whiteNPS >= 0) lastTickLength = 0;
15790         timeRemaining = whiteTimeRemaining -= lastTickLength;
15791         if(timeRemaining < 0 && !appData.icsActive) {
15792             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15793             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15794                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15795                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15796             }
15797         }
15798         DisplayWhiteClock(whiteTimeRemaining - fudge,
15799                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15800     } else {
15801         if(blackNPS >= 0) lastTickLength = 0;
15802         timeRemaining = blackTimeRemaining -= lastTickLength;
15803         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15804             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15805             if(suddenDeath) {
15806                 blackStartMove = forwardMostMove;
15807                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15808             }
15809         }
15810         DisplayBlackClock(blackTimeRemaining - fudge,
15811                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15812     }
15813     if (CheckFlags()) return;
15814
15815     tickStartTM = now;
15816     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15817     StartClockTimer(intendedTickLength);
15818
15819     /* if the time remaining has fallen below the alarm threshold, sound the
15820      * alarm. if the alarm has sounded and (due to a takeback or time control
15821      * with increment) the time remaining has increased to a level above the
15822      * threshold, reset the alarm so it can sound again.
15823      */
15824
15825     if (appData.icsActive && appData.icsAlarm) {
15826
15827         /* make sure we are dealing with the user's clock */
15828         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15829                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15830            )) return;
15831
15832         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15833             alarmSounded = FALSE;
15834         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15835             PlayAlarmSound();
15836             alarmSounded = TRUE;
15837         }
15838     }
15839 }
15840
15841
15842 /* A player has just moved, so stop the previously running
15843    clock and (if in clock mode) start the other one.
15844    We redisplay both clocks in case we're in ICS mode, because
15845    ICS gives us an update to both clocks after every move.
15846    Note that this routine is called *after* forwardMostMove
15847    is updated, so the last fractional tick must be subtracted
15848    from the color that is *not* on move now.
15849 */
15850 void
15851 SwitchClocks(int newMoveNr)
15852 {
15853     long lastTickLength;
15854     TimeMark now;
15855     int flagged = FALSE;
15856
15857     GetTimeMark(&now);
15858
15859     if (StopClockTimer() && appData.clockMode) {
15860         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15861         if (!WhiteOnMove(forwardMostMove)) {
15862             if(blackNPS >= 0) lastTickLength = 0;
15863             blackTimeRemaining -= lastTickLength;
15864            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15865 //         if(pvInfoList[forwardMostMove].time == -1)
15866                  pvInfoList[forwardMostMove].time =               // use GUI time
15867                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15868         } else {
15869            if(whiteNPS >= 0) lastTickLength = 0;
15870            whiteTimeRemaining -= lastTickLength;
15871            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15872 //         if(pvInfoList[forwardMostMove].time == -1)
15873                  pvInfoList[forwardMostMove].time =
15874                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15875         }
15876         flagged = CheckFlags();
15877     }
15878     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15879     CheckTimeControl();
15880
15881     if (flagged || !appData.clockMode) return;
15882
15883     switch (gameMode) {
15884       case MachinePlaysBlack:
15885       case MachinePlaysWhite:
15886       case BeginningOfGame:
15887         if (pausing) return;
15888         break;
15889
15890       case EditGame:
15891       case PlayFromGameFile:
15892       case IcsExamining:
15893         return;
15894
15895       default:
15896         break;
15897     }
15898
15899     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15900         if(WhiteOnMove(forwardMostMove))
15901              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15902         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15903     }
15904
15905     tickStartTM = now;
15906     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15907       whiteTimeRemaining : blackTimeRemaining);
15908     StartClockTimer(intendedTickLength);
15909 }
15910
15911
15912 /* Stop both clocks */
15913 void
15914 StopClocks()
15915 {
15916     long lastTickLength;
15917     TimeMark now;
15918
15919     if (!StopClockTimer()) return;
15920     if (!appData.clockMode) return;
15921
15922     GetTimeMark(&now);
15923
15924     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15925     if (WhiteOnMove(forwardMostMove)) {
15926         if(whiteNPS >= 0) lastTickLength = 0;
15927         whiteTimeRemaining -= lastTickLength;
15928         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15929     } else {
15930         if(blackNPS >= 0) lastTickLength = 0;
15931         blackTimeRemaining -= lastTickLength;
15932         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15933     }
15934     CheckFlags();
15935 }
15936
15937 /* Start clock of player on move.  Time may have been reset, so
15938    if clock is already running, stop and restart it. */
15939 void
15940 StartClocks()
15941 {
15942     (void) StopClockTimer(); /* in case it was running already */
15943     DisplayBothClocks();
15944     if (CheckFlags()) return;
15945
15946     if (!appData.clockMode) return;
15947     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15948
15949     GetTimeMark(&tickStartTM);
15950     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15951       whiteTimeRemaining : blackTimeRemaining);
15952
15953    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15954     whiteNPS = blackNPS = -1;
15955     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15956        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15957         whiteNPS = first.nps;
15958     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15959        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15960         blackNPS = first.nps;
15961     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15962         whiteNPS = second.nps;
15963     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15964         blackNPS = second.nps;
15965     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15966
15967     StartClockTimer(intendedTickLength);
15968 }
15969
15970 char *
15971 TimeString(ms)
15972      long ms;
15973 {
15974     long second, minute, hour, day;
15975     char *sign = "";
15976     static char buf[32];
15977
15978     if (ms > 0 && ms <= 9900) {
15979       /* convert milliseconds to tenths, rounding up */
15980       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15981
15982       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15983       return buf;
15984     }
15985
15986     /* convert milliseconds to seconds, rounding up */
15987     /* use floating point to avoid strangeness of integer division
15988        with negative dividends on many machines */
15989     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15990
15991     if (second < 0) {
15992         sign = "-";
15993         second = -second;
15994     }
15995
15996     day = second / (60 * 60 * 24);
15997     second = second % (60 * 60 * 24);
15998     hour = second / (60 * 60);
15999     second = second % (60 * 60);
16000     minute = second / 60;
16001     second = second % 60;
16002
16003     if (day > 0)
16004       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16005               sign, day, hour, minute, second);
16006     else if (hour > 0)
16007       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16008     else
16009       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16010
16011     return buf;
16012 }
16013
16014
16015 /*
16016  * This is necessary because some C libraries aren't ANSI C compliant yet.
16017  */
16018 char *
16019 StrStr(string, match)
16020      char *string, *match;
16021 {
16022     int i, length;
16023
16024     length = strlen(match);
16025
16026     for (i = strlen(string) - length; i >= 0; i--, string++)
16027       if (!strncmp(match, string, length))
16028         return string;
16029
16030     return NULL;
16031 }
16032
16033 char *
16034 StrCaseStr(string, match)
16035      char *string, *match;
16036 {
16037     int i, j, length;
16038
16039     length = strlen(match);
16040
16041     for (i = strlen(string) - length; i >= 0; i--, string++) {
16042         for (j = 0; j < length; j++) {
16043             if (ToLower(match[j]) != ToLower(string[j]))
16044               break;
16045         }
16046         if (j == length) return string;
16047     }
16048
16049     return NULL;
16050 }
16051
16052 #ifndef _amigados
16053 int
16054 StrCaseCmp(s1, s2)
16055      char *s1, *s2;
16056 {
16057     char c1, c2;
16058
16059     for (;;) {
16060         c1 = ToLower(*s1++);
16061         c2 = ToLower(*s2++);
16062         if (c1 > c2) return 1;
16063         if (c1 < c2) return -1;
16064         if (c1 == NULLCHAR) return 0;
16065     }
16066 }
16067
16068
16069 int
16070 ToLower(c)
16071      int c;
16072 {
16073     return isupper(c) ? tolower(c) : c;
16074 }
16075
16076
16077 int
16078 ToUpper(c)
16079      int c;
16080 {
16081     return islower(c) ? toupper(c) : c;
16082 }
16083 #endif /* !_amigados    */
16084
16085 char *
16086 StrSave(s)
16087      char *s;
16088 {
16089   char *ret;
16090
16091   if ((ret = (char *) malloc(strlen(s) + 1)))
16092     {
16093       safeStrCpy(ret, s, strlen(s)+1);
16094     }
16095   return ret;
16096 }
16097
16098 char *
16099 StrSavePtr(s, savePtr)
16100      char *s, **savePtr;
16101 {
16102     if (*savePtr) {
16103         free(*savePtr);
16104     }
16105     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16106       safeStrCpy(*savePtr, s, strlen(s)+1);
16107     }
16108     return(*savePtr);
16109 }
16110
16111 char *
16112 PGNDate()
16113 {
16114     time_t clock;
16115     struct tm *tm;
16116     char buf[MSG_SIZ];
16117
16118     clock = time((time_t *)NULL);
16119     tm = localtime(&clock);
16120     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16121             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16122     return StrSave(buf);
16123 }
16124
16125
16126 char *
16127 PositionToFEN(move, overrideCastling)
16128      int move;
16129      char *overrideCastling;
16130 {
16131     int i, j, fromX, fromY, toX, toY;
16132     int whiteToPlay;
16133     char buf[MSG_SIZ];
16134     char *p, *q;
16135     int emptycount;
16136     ChessSquare piece;
16137
16138     whiteToPlay = (gameMode == EditPosition) ?
16139       !blackPlaysFirst : (move % 2 == 0);
16140     p = buf;
16141
16142     /* Piece placement data */
16143     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16144         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16145         emptycount = 0;
16146         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16147             if (boards[move][i][j] == EmptySquare) {
16148                 emptycount++;
16149             } else { ChessSquare piece = boards[move][i][j];
16150                 if (emptycount > 0) {
16151                     if(emptycount<10) /* [HGM] can be >= 10 */
16152                         *p++ = '0' + emptycount;
16153                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16154                     emptycount = 0;
16155                 }
16156                 if(PieceToChar(piece) == '+') {
16157                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16158                     *p++ = '+';
16159                     piece = (ChessSquare)(DEMOTED piece);
16160                 }
16161                 *p++ = PieceToChar(piece);
16162                 if(p[-1] == '~') {
16163                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16164                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16165                     *p++ = '~';
16166                 }
16167             }
16168         }
16169         if (emptycount > 0) {
16170             if(emptycount<10) /* [HGM] can be >= 10 */
16171                 *p++ = '0' + emptycount;
16172             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16173             emptycount = 0;
16174         }
16175         *p++ = '/';
16176     }
16177     *(p - 1) = ' ';
16178
16179     /* [HGM] print Crazyhouse or Shogi holdings */
16180     if( gameInfo.holdingsWidth ) {
16181         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16182         q = p;
16183         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16184             piece = boards[move][i][BOARD_WIDTH-1];
16185             if( piece != EmptySquare )
16186               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16187                   *p++ = PieceToChar(piece);
16188         }
16189         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16190             piece = boards[move][BOARD_HEIGHT-i-1][0];
16191             if( piece != EmptySquare )
16192               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16193                   *p++ = PieceToChar(piece);
16194         }
16195
16196         if( q == p ) *p++ = '-';
16197         *p++ = ']';
16198         *p++ = ' ';
16199     }
16200
16201     /* Active color */
16202     *p++ = whiteToPlay ? 'w' : 'b';
16203     *p++ = ' ';
16204
16205   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16206     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16207   } else {
16208   if(nrCastlingRights) {
16209      q = p;
16210      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16211        /* [HGM] write directly from rights */
16212            if(boards[move][CASTLING][2] != NoRights &&
16213               boards[move][CASTLING][0] != NoRights   )
16214                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16215            if(boards[move][CASTLING][2] != NoRights &&
16216               boards[move][CASTLING][1] != NoRights   )
16217                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16218            if(boards[move][CASTLING][5] != NoRights &&
16219               boards[move][CASTLING][3] != NoRights   )
16220                 *p++ = boards[move][CASTLING][3] + AAA;
16221            if(boards[move][CASTLING][5] != NoRights &&
16222               boards[move][CASTLING][4] != NoRights   )
16223                 *p++ = boards[move][CASTLING][4] + AAA;
16224      } else {
16225
16226         /* [HGM] write true castling rights */
16227         if( nrCastlingRights == 6 ) {
16228             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16229                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16230             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16231                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16232             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16233                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16234             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16235                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16236         }
16237      }
16238      if (q == p) *p++ = '-'; /* No castling rights */
16239      *p++ = ' ';
16240   }
16241
16242   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16243      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16244     /* En passant target square */
16245     if (move > backwardMostMove) {
16246         fromX = moveList[move - 1][0] - AAA;
16247         fromY = moveList[move - 1][1] - ONE;
16248         toX = moveList[move - 1][2] - AAA;
16249         toY = moveList[move - 1][3] - ONE;
16250         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16251             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16252             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16253             fromX == toX) {
16254             /* 2-square pawn move just happened */
16255             *p++ = toX + AAA;
16256             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16257         } else {
16258             *p++ = '-';
16259         }
16260     } else if(move == backwardMostMove) {
16261         // [HGM] perhaps we should always do it like this, and forget the above?
16262         if((signed char)boards[move][EP_STATUS] >= 0) {
16263             *p++ = boards[move][EP_STATUS] + AAA;
16264             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16265         } else {
16266             *p++ = '-';
16267         }
16268     } else {
16269         *p++ = '-';
16270     }
16271     *p++ = ' ';
16272   }
16273   }
16274
16275     /* [HGM] find reversible plies */
16276     {   int i = 0, j=move;
16277
16278         if (appData.debugMode) { int k;
16279             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16280             for(k=backwardMostMove; k<=forwardMostMove; k++)
16281                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16282
16283         }
16284
16285         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16286         if( j == backwardMostMove ) i += initialRulePlies;
16287         sprintf(p, "%d ", i);
16288         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16289     }
16290     /* Fullmove number */
16291     sprintf(p, "%d", (move / 2) + 1);
16292
16293     return StrSave(buf);
16294 }
16295
16296 Boolean
16297 ParseFEN(board, blackPlaysFirst, fen)
16298     Board board;
16299      int *blackPlaysFirst;
16300      char *fen;
16301 {
16302     int i, j;
16303     char *p, c;
16304     int emptycount;
16305     ChessSquare piece;
16306
16307     p = fen;
16308
16309     /* [HGM] by default clear Crazyhouse holdings, if present */
16310     if(gameInfo.holdingsWidth) {
16311        for(i=0; i<BOARD_HEIGHT; i++) {
16312            board[i][0]             = EmptySquare; /* black holdings */
16313            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16314            board[i][1]             = (ChessSquare) 0; /* black counts */
16315            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16316        }
16317     }
16318
16319     /* Piece placement data */
16320     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16321         j = 0;
16322         for (;;) {
16323             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16324                 if (*p == '/') p++;
16325                 emptycount = gameInfo.boardWidth - j;
16326                 while (emptycount--)
16327                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16328                 break;
16329 #if(BOARD_FILES >= 10)
16330             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16331                 p++; emptycount=10;
16332                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16333                 while (emptycount--)
16334                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16335 #endif
16336             } else if (isdigit(*p)) {
16337                 emptycount = *p++ - '0';
16338                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16339                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16340                 while (emptycount--)
16341                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16342             } else if (*p == '+' || isalpha(*p)) {
16343                 if (j >= gameInfo.boardWidth) return FALSE;
16344                 if(*p=='+') {
16345                     piece = CharToPiece(*++p);
16346                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16347                     piece = (ChessSquare) (PROMOTED piece ); p++;
16348                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16349                 } else piece = CharToPiece(*p++);
16350
16351                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16352                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16353                     piece = (ChessSquare) (PROMOTED piece);
16354                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16355                     p++;
16356                 }
16357                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16358             } else {
16359                 return FALSE;
16360             }
16361         }
16362     }
16363     while (*p == '/' || *p == ' ') p++;
16364
16365     /* [HGM] look for Crazyhouse holdings here */
16366     while(*p==' ') p++;
16367     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16368         if(*p == '[') p++;
16369         if(*p == '-' ) p++; /* empty holdings */ else {
16370             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16371             /* if we would allow FEN reading to set board size, we would   */
16372             /* have to add holdings and shift the board read so far here   */
16373             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16374                 p++;
16375                 if((int) piece >= (int) BlackPawn ) {
16376                     i = (int)piece - (int)BlackPawn;
16377                     i = PieceToNumber((ChessSquare)i);
16378                     if( i >= gameInfo.holdingsSize ) return FALSE;
16379                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16380                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16381                 } else {
16382                     i = (int)piece - (int)WhitePawn;
16383                     i = PieceToNumber((ChessSquare)i);
16384                     if( i >= gameInfo.holdingsSize ) return FALSE;
16385                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16386                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16387                 }
16388             }
16389         }
16390         if(*p == ']') p++;
16391     }
16392
16393     while(*p == ' ') p++;
16394
16395     /* Active color */
16396     c = *p++;
16397     if(appData.colorNickNames) {
16398       if( c == appData.colorNickNames[0] ) c = 'w'; else
16399       if( c == appData.colorNickNames[1] ) c = 'b';
16400     }
16401     switch (c) {
16402       case 'w':
16403         *blackPlaysFirst = FALSE;
16404         break;
16405       case 'b':
16406         *blackPlaysFirst = TRUE;
16407         break;
16408       default:
16409         return FALSE;
16410     }
16411
16412     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16413     /* return the extra info in global variiables             */
16414
16415     /* set defaults in case FEN is incomplete */
16416     board[EP_STATUS] = EP_UNKNOWN;
16417     for(i=0; i<nrCastlingRights; i++ ) {
16418         board[CASTLING][i] =
16419             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16420     }   /* assume possible unless obviously impossible */
16421     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16422     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16423     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16424                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16425     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16426     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16427     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16428                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16429     FENrulePlies = 0;
16430
16431     while(*p==' ') p++;
16432     if(nrCastlingRights) {
16433       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16434           /* castling indicator present, so default becomes no castlings */
16435           for(i=0; i<nrCastlingRights; i++ ) {
16436                  board[CASTLING][i] = NoRights;
16437           }
16438       }
16439       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16440              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16441              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16442              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16443         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16444
16445         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16446             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16447             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16448         }
16449         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16450             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16451         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16452                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16453         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16454                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16455         switch(c) {
16456           case'K':
16457               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16458               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16459               board[CASTLING][2] = whiteKingFile;
16460               break;
16461           case'Q':
16462               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16463               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16464               board[CASTLING][2] = whiteKingFile;
16465               break;
16466           case'k':
16467               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16468               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16469               board[CASTLING][5] = blackKingFile;
16470               break;
16471           case'q':
16472               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16473               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16474               board[CASTLING][5] = blackKingFile;
16475           case '-':
16476               break;
16477           default: /* FRC castlings */
16478               if(c >= 'a') { /* black rights */
16479                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16480                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16481                   if(i == BOARD_RGHT) break;
16482                   board[CASTLING][5] = i;
16483                   c -= AAA;
16484                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16485                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16486                   if(c > i)
16487                       board[CASTLING][3] = c;
16488                   else
16489                       board[CASTLING][4] = c;
16490               } else { /* white rights */
16491                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16492                     if(board[0][i] == WhiteKing) break;
16493                   if(i == BOARD_RGHT) break;
16494                   board[CASTLING][2] = i;
16495                   c -= AAA - 'a' + 'A';
16496                   if(board[0][c] >= WhiteKing) break;
16497                   if(c > i)
16498                       board[CASTLING][0] = c;
16499                   else
16500                       board[CASTLING][1] = c;
16501               }
16502         }
16503       }
16504       for(i=0; i<nrCastlingRights; i++)
16505         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16506     if (appData.debugMode) {
16507         fprintf(debugFP, "FEN castling rights:");
16508         for(i=0; i<nrCastlingRights; i++)
16509         fprintf(debugFP, " %d", board[CASTLING][i]);
16510         fprintf(debugFP, "\n");
16511     }
16512
16513       while(*p==' ') p++;
16514     }
16515
16516     /* read e.p. field in games that know e.p. capture */
16517     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16518        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16519       if(*p=='-') {
16520         p++; board[EP_STATUS] = EP_NONE;
16521       } else {
16522          char c = *p++ - AAA;
16523
16524          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16525          if(*p >= '0' && *p <='9') p++;
16526          board[EP_STATUS] = c;
16527       }
16528     }
16529
16530
16531     if(sscanf(p, "%d", &i) == 1) {
16532         FENrulePlies = i; /* 50-move ply counter */
16533         /* (The move number is still ignored)    */
16534     }
16535
16536     return TRUE;
16537 }
16538
16539 void
16540 EditPositionPasteFEN(char *fen)
16541 {
16542   if (fen != NULL) {
16543     Board initial_position;
16544
16545     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16546       DisplayError(_("Bad FEN position in clipboard"), 0);
16547       return ;
16548     } else {
16549       int savedBlackPlaysFirst = blackPlaysFirst;
16550       EditPositionEvent();
16551       blackPlaysFirst = savedBlackPlaysFirst;
16552       CopyBoard(boards[0], initial_position);
16553       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16554       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16555       DisplayBothClocks();
16556       DrawPosition(FALSE, boards[currentMove]);
16557     }
16558   }
16559 }
16560
16561 static char cseq[12] = "\\   ";
16562
16563 Boolean set_cont_sequence(char *new_seq)
16564 {
16565     int len;
16566     Boolean ret;
16567
16568     // handle bad attempts to set the sequence
16569         if (!new_seq)
16570                 return 0; // acceptable error - no debug
16571
16572     len = strlen(new_seq);
16573     ret = (len > 0) && (len < sizeof(cseq));
16574     if (ret)
16575       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16576     else if (appData.debugMode)
16577       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16578     return ret;
16579 }
16580
16581 /*
16582     reformat a source message so words don't cross the width boundary.  internal
16583     newlines are not removed.  returns the wrapped size (no null character unless
16584     included in source message).  If dest is NULL, only calculate the size required
16585     for the dest buffer.  lp argument indicats line position upon entry, and it's
16586     passed back upon exit.
16587 */
16588 int wrap(char *dest, char *src, int count, int width, int *lp)
16589 {
16590     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16591
16592     cseq_len = strlen(cseq);
16593     old_line = line = *lp;
16594     ansi = len = clen = 0;
16595
16596     for (i=0; i < count; i++)
16597     {
16598         if (src[i] == '\033')
16599             ansi = 1;
16600
16601         // if we hit the width, back up
16602         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16603         {
16604             // store i & len in case the word is too long
16605             old_i = i, old_len = len;
16606
16607             // find the end of the last word
16608             while (i && src[i] != ' ' && src[i] != '\n')
16609             {
16610                 i--;
16611                 len--;
16612             }
16613
16614             // word too long?  restore i & len before splitting it
16615             if ((old_i-i+clen) >= width)
16616             {
16617                 i = old_i;
16618                 len = old_len;
16619             }
16620
16621             // extra space?
16622             if (i && src[i-1] == ' ')
16623                 len--;
16624
16625             if (src[i] != ' ' && src[i] != '\n')
16626             {
16627                 i--;
16628                 if (len)
16629                     len--;
16630             }
16631
16632             // now append the newline and continuation sequence
16633             if (dest)
16634                 dest[len] = '\n';
16635             len++;
16636             if (dest)
16637                 strncpy(dest+len, cseq, cseq_len);
16638             len += cseq_len;
16639             line = cseq_len;
16640             clen = cseq_len;
16641             continue;
16642         }
16643
16644         if (dest)
16645             dest[len] = src[i];
16646         len++;
16647         if (!ansi)
16648             line++;
16649         if (src[i] == '\n')
16650             line = 0;
16651         if (src[i] == 'm')
16652             ansi = 0;
16653     }
16654     if (dest && appData.debugMode)
16655     {
16656         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16657             count, width, line, len, *lp);
16658         show_bytes(debugFP, src, count);
16659         fprintf(debugFP, "\ndest: ");
16660         show_bytes(debugFP, dest, len);
16661         fprintf(debugFP, "\n");
16662     }
16663     *lp = dest ? line : old_line;
16664
16665     return len;
16666 }
16667
16668 // [HGM] vari: routines for shelving variations
16669 Boolean modeRestore = FALSE;
16670
16671 void
16672 PushInner(int firstMove, int lastMove)
16673 {
16674         int i, j, nrMoves = lastMove - firstMove;
16675
16676         // push current tail of game on stack
16677         savedResult[storedGames] = gameInfo.result;
16678         savedDetails[storedGames] = gameInfo.resultDetails;
16679         gameInfo.resultDetails = NULL;
16680         savedFirst[storedGames] = firstMove;
16681         savedLast [storedGames] = lastMove;
16682         savedFramePtr[storedGames] = framePtr;
16683         framePtr -= nrMoves; // reserve space for the boards
16684         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16685             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16686             for(j=0; j<MOVE_LEN; j++)
16687                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16688             for(j=0; j<2*MOVE_LEN; j++)
16689                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16690             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16691             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16692             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16693             pvInfoList[firstMove+i-1].depth = 0;
16694             commentList[framePtr+i] = commentList[firstMove+i];
16695             commentList[firstMove+i] = NULL;
16696         }
16697
16698         storedGames++;
16699         forwardMostMove = firstMove; // truncate game so we can start variation
16700 }
16701
16702 void
16703 PushTail(int firstMove, int lastMove)
16704 {
16705         if(appData.icsActive) { // only in local mode
16706                 forwardMostMove = currentMove; // mimic old ICS behavior
16707                 return;
16708         }
16709         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16710
16711         PushInner(firstMove, lastMove);
16712         if(storedGames == 1) GreyRevert(FALSE);
16713         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16714 }
16715
16716 void
16717 PopInner(Boolean annotate)
16718 {
16719         int i, j, nrMoves;
16720         char buf[8000], moveBuf[20];
16721
16722         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16723         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16724         nrMoves = savedLast[storedGames] - currentMove;
16725         if(annotate) {
16726                 int cnt = 10;
16727                 if(!WhiteOnMove(currentMove))
16728                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16729                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16730                 for(i=currentMove; i<forwardMostMove; i++) {
16731                         if(WhiteOnMove(i))
16732                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16733                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16734                         strcat(buf, moveBuf);
16735                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16736                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16737                 }
16738                 strcat(buf, ")");
16739         }
16740         for(i=1; i<=nrMoves; i++) { // copy last variation back
16741             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16742             for(j=0; j<MOVE_LEN; j++)
16743                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16744             for(j=0; j<2*MOVE_LEN; j++)
16745                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16746             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16747             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16748             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16749             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16750             commentList[currentMove+i] = commentList[framePtr+i];
16751             commentList[framePtr+i] = NULL;
16752         }
16753         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16754         framePtr = savedFramePtr[storedGames];
16755         gameInfo.result = savedResult[storedGames];
16756         if(gameInfo.resultDetails != NULL) {
16757             free(gameInfo.resultDetails);
16758       }
16759         gameInfo.resultDetails = savedDetails[storedGames];
16760         forwardMostMove = currentMove + nrMoves;
16761 }
16762
16763 Boolean
16764 PopTail(Boolean annotate)
16765 {
16766         if(appData.icsActive) return FALSE; // only in local mode
16767         if(!storedGames) return FALSE; // sanity
16768         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16769
16770         PopInner(annotate);
16771         if(currentMove < forwardMostMove) ForwardEvent(); else
16772         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16773
16774         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16775         return TRUE;
16776 }
16777
16778 void
16779 CleanupTail()
16780 {       // remove all shelved variations
16781         int i;
16782         for(i=0; i<storedGames; i++) {
16783             if(savedDetails[i])
16784                 free(savedDetails[i]);
16785             savedDetails[i] = NULL;
16786         }
16787         for(i=framePtr; i<MAX_MOVES; i++) {
16788                 if(commentList[i]) free(commentList[i]);
16789                 commentList[i] = NULL;
16790         }
16791         framePtr = MAX_MOVES-1;
16792         storedGames = 0;
16793 }
16794
16795 void
16796 LoadVariation(int index, char *text)
16797 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16798         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16799         int level = 0, move;
16800
16801         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16802         // first find outermost bracketing variation
16803         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16804             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16805                 if(*p == '{') wait = '}'; else
16806                 if(*p == '[') wait = ']'; else
16807                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16808                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16809             }
16810             if(*p == wait) wait = NULLCHAR; // closing ]} found
16811             p++;
16812         }
16813         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16814         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16815         end[1] = NULLCHAR; // clip off comment beyond variation
16816         ToNrEvent(currentMove-1);
16817         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16818         // kludge: use ParsePV() to append variation to game
16819         move = currentMove;
16820         ParsePV(start, TRUE, TRUE);
16821         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16822         ClearPremoveHighlights();
16823         CommentPopDown();
16824         ToNrEvent(currentMove+1);
16825 }
16826