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