Move HistorySet to back-end
[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;
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;
11959
11960     if (strcmp(filename, "-") == 0) {
11961         return SaveGame(stdout, 0, NULL);
11962     } else {
11963         f = fopen(filename, append ? "a" : "w");
11964         if (f == NULL) {
11965             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11966             DisplayError(buf, errno);
11967             return FALSE;
11968         } else {
11969             safeStrCpy(buf, lastMsg, MSG_SIZ);
11970             DisplayMessage(_("Waiting for access to save file"), "");
11971             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11972             DisplayMessage(_("Saving game"), "");
11973             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11974             result = SaveGame(f, 0, NULL);
11975             DisplayMessage(buf, "");
11976             return result;
11977         }
11978     }
11979 }
11980
11981 char *
11982 SavePart(str)
11983      char *str;
11984 {
11985     static char buf[MSG_SIZ];
11986     char *p;
11987
11988     p = strchr(str, ' ');
11989     if (p == NULL) return str;
11990     strncpy(buf, str, p - str);
11991     buf[p - str] = NULLCHAR;
11992     return buf;
11993 }
11994
11995 #define PGN_MAX_LINE 75
11996
11997 #define PGN_SIDE_WHITE  0
11998 #define PGN_SIDE_BLACK  1
11999
12000 /* [AS] */
12001 static int FindFirstMoveOutOfBook( int side )
12002 {
12003     int result = -1;
12004
12005     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12006         int index = backwardMostMove;
12007         int has_book_hit = 0;
12008
12009         if( (index % 2) != side ) {
12010             index++;
12011         }
12012
12013         while( index < forwardMostMove ) {
12014             /* Check to see if engine is in book */
12015             int depth = pvInfoList[index].depth;
12016             int score = pvInfoList[index].score;
12017             int in_book = 0;
12018
12019             if( depth <= 2 ) {
12020                 in_book = 1;
12021             }
12022             else if( score == 0 && depth == 63 ) {
12023                 in_book = 1; /* Zappa */
12024             }
12025             else if( score == 2 && depth == 99 ) {
12026                 in_book = 1; /* Abrok */
12027             }
12028
12029             has_book_hit += in_book;
12030
12031             if( ! in_book ) {
12032                 result = index;
12033
12034                 break;
12035             }
12036
12037             index += 2;
12038         }
12039     }
12040
12041     return result;
12042 }
12043
12044 /* [AS] */
12045 void GetOutOfBookInfo( char * buf )
12046 {
12047     int oob[2];
12048     int i;
12049     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12050
12051     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12052     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12053
12054     *buf = '\0';
12055
12056     if( oob[0] >= 0 || oob[1] >= 0 ) {
12057         for( i=0; i<2; i++ ) {
12058             int idx = oob[i];
12059
12060             if( idx >= 0 ) {
12061                 if( i > 0 && oob[0] >= 0 ) {
12062                     strcat( buf, "   " );
12063                 }
12064
12065                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12066                 sprintf( buf+strlen(buf), "%s%.2f",
12067                     pvInfoList[idx].score >= 0 ? "+" : "",
12068                     pvInfoList[idx].score / 100.0 );
12069             }
12070         }
12071     }
12072 }
12073
12074 /* Save game in PGN style and close the file */
12075 int
12076 SaveGamePGN(f)
12077      FILE *f;
12078 {
12079     int i, offset, linelen, newblock;
12080     time_t tm;
12081 //    char *movetext;
12082     char numtext[32];
12083     int movelen, numlen, blank;
12084     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12085
12086     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12087
12088     tm = time((time_t *) NULL);
12089
12090     PrintPGNTags(f, &gameInfo);
12091
12092     if (backwardMostMove > 0 || startedFromSetupPosition) {
12093         char *fen = PositionToFEN(backwardMostMove, NULL);
12094         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12095         fprintf(f, "\n{--------------\n");
12096         PrintPosition(f, backwardMostMove);
12097         fprintf(f, "--------------}\n");
12098         free(fen);
12099     }
12100     else {
12101         /* [AS] Out of book annotation */
12102         if( appData.saveOutOfBookInfo ) {
12103             char buf[64];
12104
12105             GetOutOfBookInfo( buf );
12106
12107             if( buf[0] != '\0' ) {
12108                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12109             }
12110         }
12111
12112         fprintf(f, "\n");
12113     }
12114
12115     i = backwardMostMove;
12116     linelen = 0;
12117     newblock = TRUE;
12118
12119     while (i < forwardMostMove) {
12120         /* Print comments preceding this move */
12121         if (commentList[i] != NULL) {
12122             if (linelen > 0) fprintf(f, "\n");
12123             fprintf(f, "%s", commentList[i]);
12124             linelen = 0;
12125             newblock = TRUE;
12126         }
12127
12128         /* Format move number */
12129         if ((i % 2) == 0)
12130           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12131         else
12132           if (newblock)
12133             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12134           else
12135             numtext[0] = NULLCHAR;
12136
12137         numlen = strlen(numtext);
12138         newblock = FALSE;
12139
12140         /* Print move number */
12141         blank = linelen > 0 && numlen > 0;
12142         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12143             fprintf(f, "\n");
12144             linelen = 0;
12145             blank = 0;
12146         }
12147         if (blank) {
12148             fprintf(f, " ");
12149             linelen++;
12150         }
12151         fprintf(f, "%s", numtext);
12152         linelen += numlen;
12153
12154         /* Get move */
12155         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12156         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12157
12158         /* Print move */
12159         blank = linelen > 0 && movelen > 0;
12160         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12161             fprintf(f, "\n");
12162             linelen = 0;
12163             blank = 0;
12164         }
12165         if (blank) {
12166             fprintf(f, " ");
12167             linelen++;
12168         }
12169         fprintf(f, "%s", move_buffer);
12170         linelen += movelen;
12171
12172         /* [AS] Add PV info if present */
12173         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12174             /* [HGM] add time */
12175             char buf[MSG_SIZ]; int seconds;
12176
12177             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12178
12179             if( seconds <= 0)
12180               buf[0] = 0;
12181             else
12182               if( seconds < 30 )
12183                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12184               else
12185                 {
12186                   seconds = (seconds + 4)/10; // round to full seconds
12187                   if( seconds < 60 )
12188                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12189                   else
12190                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12191                 }
12192
12193             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12194                       pvInfoList[i].score >= 0 ? "+" : "",
12195                       pvInfoList[i].score / 100.0,
12196                       pvInfoList[i].depth,
12197                       buf );
12198
12199             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12200
12201             /* Print score/depth */
12202             blank = linelen > 0 && movelen > 0;
12203             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12204                 fprintf(f, "\n");
12205                 linelen = 0;
12206                 blank = 0;
12207             }
12208             if (blank) {
12209                 fprintf(f, " ");
12210                 linelen++;
12211             }
12212             fprintf(f, "%s", move_buffer);
12213             linelen += movelen;
12214         }
12215
12216         i++;
12217     }
12218
12219     /* Start a new line */
12220     if (linelen > 0) fprintf(f, "\n");
12221
12222     /* Print comments after last move */
12223     if (commentList[i] != NULL) {
12224         fprintf(f, "%s\n", commentList[i]);
12225     }
12226
12227     /* Print result */
12228     if (gameInfo.resultDetails != NULL &&
12229         gameInfo.resultDetails[0] != NULLCHAR) {
12230         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12231                 PGNResult(gameInfo.result));
12232     } else {
12233         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12234     }
12235
12236     fclose(f);
12237     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12238     return TRUE;
12239 }
12240
12241 /* Save game in old style and close the file */
12242 int
12243 SaveGameOldStyle(f)
12244      FILE *f;
12245 {
12246     int i, offset;
12247     time_t tm;
12248
12249     tm = time((time_t *) NULL);
12250
12251     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12252     PrintOpponents(f);
12253
12254     if (backwardMostMove > 0 || startedFromSetupPosition) {
12255         fprintf(f, "\n[--------------\n");
12256         PrintPosition(f, backwardMostMove);
12257         fprintf(f, "--------------]\n");
12258     } else {
12259         fprintf(f, "\n");
12260     }
12261
12262     i = backwardMostMove;
12263     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12264
12265     while (i < forwardMostMove) {
12266         if (commentList[i] != NULL) {
12267             fprintf(f, "[%s]\n", commentList[i]);
12268         }
12269
12270         if ((i % 2) == 1) {
12271             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12272             i++;
12273         } else {
12274             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12275             i++;
12276             if (commentList[i] != NULL) {
12277                 fprintf(f, "\n");
12278                 continue;
12279             }
12280             if (i >= forwardMostMove) {
12281                 fprintf(f, "\n");
12282                 break;
12283             }
12284             fprintf(f, "%s\n", parseList[i]);
12285             i++;
12286         }
12287     }
12288
12289     if (commentList[i] != NULL) {
12290         fprintf(f, "[%s]\n", commentList[i]);
12291     }
12292
12293     /* This isn't really the old style, but it's close enough */
12294     if (gameInfo.resultDetails != NULL &&
12295         gameInfo.resultDetails[0] != NULLCHAR) {
12296         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12297                 gameInfo.resultDetails);
12298     } else {
12299         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12300     }
12301
12302     fclose(f);
12303     return TRUE;
12304 }
12305
12306 /* Save the current game to open file f and close the file */
12307 int
12308 SaveGame(f, dummy, dummy2)
12309      FILE *f;
12310      int dummy;
12311      char *dummy2;
12312 {
12313     if (gameMode == EditPosition) EditPositionDone(TRUE);
12314     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12315     if (appData.oldSaveStyle)
12316       return SaveGameOldStyle(f);
12317     else
12318       return SaveGamePGN(f);
12319 }
12320
12321 /* Save the current position to the given file */
12322 int
12323 SavePositionToFile(filename)
12324      char *filename;
12325 {
12326     FILE *f;
12327     char buf[MSG_SIZ];
12328
12329     if (strcmp(filename, "-") == 0) {
12330         return SavePosition(stdout, 0, NULL);
12331     } else {
12332         f = fopen(filename, "a");
12333         if (f == NULL) {
12334             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12335             DisplayError(buf, errno);
12336             return FALSE;
12337         } else {
12338             safeStrCpy(buf, lastMsg, MSG_SIZ);
12339             DisplayMessage(_("Waiting for access to save file"), "");
12340             flock(fileno(f), LOCK_EX); // [HGM] lock
12341             DisplayMessage(_("Saving position"), "");
12342             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12343             SavePosition(f, 0, NULL);
12344             DisplayMessage(buf, "");
12345             return TRUE;
12346         }
12347     }
12348 }
12349
12350 /* Save the current position to the given open file and close the file */
12351 int
12352 SavePosition(f, dummy, dummy2)
12353      FILE *f;
12354      int dummy;
12355      char *dummy2;
12356 {
12357     time_t tm;
12358     char *fen;
12359
12360     if (gameMode == EditPosition) EditPositionDone(TRUE);
12361     if (appData.oldSaveStyle) {
12362         tm = time((time_t *) NULL);
12363
12364         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12365         PrintOpponents(f);
12366         fprintf(f, "[--------------\n");
12367         PrintPosition(f, currentMove);
12368         fprintf(f, "--------------]\n");
12369     } else {
12370         fen = PositionToFEN(currentMove, NULL);
12371         fprintf(f, "%s\n", fen);
12372         free(fen);
12373     }
12374     fclose(f);
12375     return TRUE;
12376 }
12377
12378 void
12379 ReloadCmailMsgEvent(unregister)
12380      int unregister;
12381 {
12382 #if !WIN32
12383     static char *inFilename = NULL;
12384     static char *outFilename;
12385     int i;
12386     struct stat inbuf, outbuf;
12387     int status;
12388
12389     /* Any registered moves are unregistered if unregister is set, */
12390     /* i.e. invoked by the signal handler */
12391     if (unregister) {
12392         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12393             cmailMoveRegistered[i] = FALSE;
12394             if (cmailCommentList[i] != NULL) {
12395                 free(cmailCommentList[i]);
12396                 cmailCommentList[i] = NULL;
12397             }
12398         }
12399         nCmailMovesRegistered = 0;
12400     }
12401
12402     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12403         cmailResult[i] = CMAIL_NOT_RESULT;
12404     }
12405     nCmailResults = 0;
12406
12407     if (inFilename == NULL) {
12408         /* Because the filenames are static they only get malloced once  */
12409         /* and they never get freed                                      */
12410         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12411         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12412
12413         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12414         sprintf(outFilename, "%s.out", appData.cmailGameName);
12415     }
12416
12417     status = stat(outFilename, &outbuf);
12418     if (status < 0) {
12419         cmailMailedMove = FALSE;
12420     } else {
12421         status = stat(inFilename, &inbuf);
12422         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12423     }
12424
12425     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12426        counts the games, notes how each one terminated, etc.
12427
12428        It would be nice to remove this kludge and instead gather all
12429        the information while building the game list.  (And to keep it
12430        in the game list nodes instead of having a bunch of fixed-size
12431        parallel arrays.)  Note this will require getting each game's
12432        termination from the PGN tags, as the game list builder does
12433        not process the game moves.  --mann
12434        */
12435     cmailMsgLoaded = TRUE;
12436     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12437
12438     /* Load first game in the file or popup game menu */
12439     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12440
12441 #endif /* !WIN32 */
12442     return;
12443 }
12444
12445 int
12446 RegisterMove()
12447 {
12448     FILE *f;
12449     char string[MSG_SIZ];
12450
12451     if (   cmailMailedMove
12452         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12453         return TRUE;            /* Allow free viewing  */
12454     }
12455
12456     /* Unregister move to ensure that we don't leave RegisterMove        */
12457     /* with the move registered when the conditions for registering no   */
12458     /* longer hold                                                       */
12459     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12460         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12461         nCmailMovesRegistered --;
12462
12463         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12464           {
12465               free(cmailCommentList[lastLoadGameNumber - 1]);
12466               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12467           }
12468     }
12469
12470     if (cmailOldMove == -1) {
12471         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12472         return FALSE;
12473     }
12474
12475     if (currentMove > cmailOldMove + 1) {
12476         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12477         return FALSE;
12478     }
12479
12480     if (currentMove < cmailOldMove) {
12481         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12482         return FALSE;
12483     }
12484
12485     if (forwardMostMove > currentMove) {
12486         /* Silently truncate extra moves */
12487         TruncateGame();
12488     }
12489
12490     if (   (currentMove == cmailOldMove + 1)
12491         || (   (currentMove == cmailOldMove)
12492             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12493                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12494         if (gameInfo.result != GameUnfinished) {
12495             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12496         }
12497
12498         if (commentList[currentMove] != NULL) {
12499             cmailCommentList[lastLoadGameNumber - 1]
12500               = StrSave(commentList[currentMove]);
12501         }
12502         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12503
12504         if (appData.debugMode)
12505           fprintf(debugFP, "Saving %s for game %d\n",
12506                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12507
12508         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12509
12510         f = fopen(string, "w");
12511         if (appData.oldSaveStyle) {
12512             SaveGameOldStyle(f); /* also closes the file */
12513
12514             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12515             f = fopen(string, "w");
12516             SavePosition(f, 0, NULL); /* also closes the file */
12517         } else {
12518             fprintf(f, "{--------------\n");
12519             PrintPosition(f, currentMove);
12520             fprintf(f, "--------------}\n\n");
12521
12522             SaveGame(f, 0, NULL); /* also closes the file*/
12523         }
12524
12525         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12526         nCmailMovesRegistered ++;
12527     } else if (nCmailGames == 1) {
12528         DisplayError(_("You have not made a move yet"), 0);
12529         return FALSE;
12530     }
12531
12532     return TRUE;
12533 }
12534
12535 void
12536 MailMoveEvent()
12537 {
12538 #if !WIN32
12539     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12540     FILE *commandOutput;
12541     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12542     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12543     int nBuffers;
12544     int i;
12545     int archived;
12546     char *arcDir;
12547
12548     if (! cmailMsgLoaded) {
12549         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12550         return;
12551     }
12552
12553     if (nCmailGames == nCmailResults) {
12554         DisplayError(_("No unfinished games"), 0);
12555         return;
12556     }
12557
12558 #if CMAIL_PROHIBIT_REMAIL
12559     if (cmailMailedMove) {
12560       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);
12561         DisplayError(msg, 0);
12562         return;
12563     }
12564 #endif
12565
12566     if (! (cmailMailedMove || RegisterMove())) return;
12567
12568     if (   cmailMailedMove
12569         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12570       snprintf(string, MSG_SIZ, partCommandString,
12571                appData.debugMode ? " -v" : "", appData.cmailGameName);
12572         commandOutput = popen(string, "r");
12573
12574         if (commandOutput == NULL) {
12575             DisplayError(_("Failed to invoke cmail"), 0);
12576         } else {
12577             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12578                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12579             }
12580             if (nBuffers > 1) {
12581                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12582                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12583                 nBytes = MSG_SIZ - 1;
12584             } else {
12585                 (void) memcpy(msg, buffer, nBytes);
12586             }
12587             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12588
12589             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12590                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12591
12592                 archived = TRUE;
12593                 for (i = 0; i < nCmailGames; i ++) {
12594                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12595                         archived = FALSE;
12596                     }
12597                 }
12598                 if (   archived
12599                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12600                         != NULL)) {
12601                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12602                            arcDir,
12603                            appData.cmailGameName,
12604                            gameInfo.date);
12605                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12606                     cmailMsgLoaded = FALSE;
12607                 }
12608             }
12609
12610             DisplayInformation(msg);
12611             pclose(commandOutput);
12612         }
12613     } else {
12614         if ((*cmailMsg) != '\0') {
12615             DisplayInformation(cmailMsg);
12616         }
12617     }
12618
12619     return;
12620 #endif /* !WIN32 */
12621 }
12622
12623 char *
12624 CmailMsg()
12625 {
12626 #if WIN32
12627     return NULL;
12628 #else
12629     int  prependComma = 0;
12630     char number[5];
12631     char string[MSG_SIZ];       /* Space for game-list */
12632     int  i;
12633
12634     if (!cmailMsgLoaded) return "";
12635
12636     if (cmailMailedMove) {
12637       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12638     } else {
12639         /* Create a list of games left */
12640       snprintf(string, MSG_SIZ, "[");
12641         for (i = 0; i < nCmailGames; i ++) {
12642             if (! (   cmailMoveRegistered[i]
12643                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12644                 if (prependComma) {
12645                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12646                 } else {
12647                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12648                     prependComma = 1;
12649                 }
12650
12651                 strcat(string, number);
12652             }
12653         }
12654         strcat(string, "]");
12655
12656         if (nCmailMovesRegistered + nCmailResults == 0) {
12657             switch (nCmailGames) {
12658               case 1:
12659                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12660                 break;
12661
12662               case 2:
12663                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12664                 break;
12665
12666               default:
12667                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12668                          nCmailGames);
12669                 break;
12670             }
12671         } else {
12672             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12673               case 1:
12674                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12675                          string);
12676                 break;
12677
12678               case 0:
12679                 if (nCmailResults == nCmailGames) {
12680                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12681                 } else {
12682                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12683                 }
12684                 break;
12685
12686               default:
12687                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12688                          string);
12689             }
12690         }
12691     }
12692     return cmailMsg;
12693 #endif /* WIN32 */
12694 }
12695
12696 void
12697 ResetGameEvent()
12698 {
12699     if (gameMode == Training)
12700       SetTrainingModeOff();
12701
12702     Reset(TRUE, TRUE);
12703     cmailMsgLoaded = FALSE;
12704     if (appData.icsActive) {
12705       SendToICS(ics_prefix);
12706       SendToICS("refresh\n");
12707     }
12708 }
12709
12710 void
12711 ExitEvent(status)
12712      int status;
12713 {
12714     exiting++;
12715     if (exiting > 2) {
12716       /* Give up on clean exit */
12717       exit(status);
12718     }
12719     if (exiting > 1) {
12720       /* Keep trying for clean exit */
12721       return;
12722     }
12723
12724     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12725
12726     if (telnetISR != NULL) {
12727       RemoveInputSource(telnetISR);
12728     }
12729     if (icsPR != NoProc) {
12730       DestroyChildProcess(icsPR, TRUE);
12731     }
12732
12733     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12734     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12735
12736     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12737     /* make sure this other one finishes before killing it!                  */
12738     if(endingGame) { int count = 0;
12739         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12740         while(endingGame && count++ < 10) DoSleep(1);
12741         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12742     }
12743
12744     /* Kill off chess programs */
12745     if (first.pr != NoProc) {
12746         ExitAnalyzeMode();
12747
12748         DoSleep( appData.delayBeforeQuit );
12749         SendToProgram("quit\n", &first);
12750         DoSleep( appData.delayAfterQuit );
12751         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12752     }
12753     if (second.pr != NoProc) {
12754         DoSleep( appData.delayBeforeQuit );
12755         SendToProgram("quit\n", &second);
12756         DoSleep( appData.delayAfterQuit );
12757         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12758     }
12759     if (first.isr != NULL) {
12760         RemoveInputSource(first.isr);
12761     }
12762     if (second.isr != NULL) {
12763         RemoveInputSource(second.isr);
12764     }
12765
12766     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12767     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12768
12769     ShutDownFrontEnd();
12770     exit(status);
12771 }
12772
12773 void
12774 PauseEvent()
12775 {
12776     if (appData.debugMode)
12777         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12778     if (pausing) {
12779         pausing = FALSE;
12780         ModeHighlight();
12781         if (gameMode == MachinePlaysWhite ||
12782             gameMode == MachinePlaysBlack) {
12783             StartClocks();
12784         } else {
12785             DisplayBothClocks();
12786         }
12787         if (gameMode == PlayFromGameFile) {
12788             if (appData.timeDelay >= 0)
12789                 AutoPlayGameLoop();
12790         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12791             Reset(FALSE, TRUE);
12792             SendToICS(ics_prefix);
12793             SendToICS("refresh\n");
12794         } else if (currentMove < forwardMostMove) {
12795             ForwardInner(forwardMostMove);
12796         }
12797         pauseExamInvalid = FALSE;
12798     } else {
12799         switch (gameMode) {
12800           default:
12801             return;
12802           case IcsExamining:
12803             pauseExamForwardMostMove = forwardMostMove;
12804             pauseExamInvalid = FALSE;
12805             /* fall through */
12806           case IcsObserving:
12807           case IcsPlayingWhite:
12808           case IcsPlayingBlack:
12809             pausing = TRUE;
12810             ModeHighlight();
12811             return;
12812           case PlayFromGameFile:
12813             (void) StopLoadGameTimer();
12814             pausing = TRUE;
12815             ModeHighlight();
12816             break;
12817           case BeginningOfGame:
12818             if (appData.icsActive) return;
12819             /* else fall through */
12820           case MachinePlaysWhite:
12821           case MachinePlaysBlack:
12822           case TwoMachinesPlay:
12823             if (forwardMostMove == 0)
12824               return;           /* don't pause if no one has moved */
12825             if ((gameMode == MachinePlaysWhite &&
12826                  !WhiteOnMove(forwardMostMove)) ||
12827                 (gameMode == MachinePlaysBlack &&
12828                  WhiteOnMove(forwardMostMove))) {
12829                 StopClocks();
12830             }
12831             pausing = TRUE;
12832             ModeHighlight();
12833             break;
12834         }
12835     }
12836 }
12837
12838 void
12839 EditCommentEvent()
12840 {
12841     char title[MSG_SIZ];
12842
12843     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12844       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12845     } else {
12846       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12847                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12848                parseList[currentMove - 1]);
12849     }
12850
12851     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12852 }
12853
12854
12855 void
12856 EditTagsEvent()
12857 {
12858     char *tags = PGNTags(&gameInfo);
12859     bookUp = FALSE;
12860     EditTagsPopUp(tags, NULL);
12861     free(tags);
12862 }
12863
12864 void
12865 AnalyzeModeEvent()
12866 {
12867     if (appData.noChessProgram || gameMode == AnalyzeMode)
12868       return;
12869
12870     if (gameMode != AnalyzeFile) {
12871         if (!appData.icsEngineAnalyze) {
12872                EditGameEvent();
12873                if (gameMode != EditGame) return;
12874         }
12875         ResurrectChessProgram();
12876         SendToProgram("analyze\n", &first);
12877         first.analyzing = TRUE;
12878         /*first.maybeThinking = TRUE;*/
12879         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12880         EngineOutputPopUp();
12881     }
12882     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12883     pausing = FALSE;
12884     ModeHighlight();
12885     SetGameInfo();
12886
12887     StartAnalysisClock();
12888     GetTimeMark(&lastNodeCountTime);
12889     lastNodeCount = 0;
12890 }
12891
12892 void
12893 AnalyzeFileEvent()
12894 {
12895     if (appData.noChessProgram || gameMode == AnalyzeFile)
12896       return;
12897
12898     if (gameMode != AnalyzeMode) {
12899         EditGameEvent();
12900         if (gameMode != EditGame) return;
12901         ResurrectChessProgram();
12902         SendToProgram("analyze\n", &first);
12903         first.analyzing = TRUE;
12904         /*first.maybeThinking = TRUE;*/
12905         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12906         EngineOutputPopUp();
12907     }
12908     gameMode = AnalyzeFile;
12909     pausing = FALSE;
12910     ModeHighlight();
12911     SetGameInfo();
12912
12913     StartAnalysisClock();
12914     GetTimeMark(&lastNodeCountTime);
12915     lastNodeCount = 0;
12916     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12917 }
12918
12919 void
12920 MachineWhiteEvent()
12921 {
12922     char buf[MSG_SIZ];
12923     char *bookHit = NULL;
12924
12925     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12926       return;
12927
12928
12929     if (gameMode == PlayFromGameFile ||
12930         gameMode == TwoMachinesPlay  ||
12931         gameMode == Training         ||
12932         gameMode == AnalyzeMode      ||
12933         gameMode == EndOfGame)
12934         EditGameEvent();
12935
12936     if (gameMode == EditPosition)
12937         EditPositionDone(TRUE);
12938
12939     if (!WhiteOnMove(currentMove)) {
12940         DisplayError(_("It is not White's turn"), 0);
12941         return;
12942     }
12943
12944     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12945       ExitAnalyzeMode();
12946
12947     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12948         gameMode == AnalyzeFile)
12949         TruncateGame();
12950
12951     ResurrectChessProgram();    /* in case it isn't running */
12952     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12953         gameMode = MachinePlaysWhite;
12954         ResetClocks();
12955     } else
12956     gameMode = MachinePlaysWhite;
12957     pausing = FALSE;
12958     ModeHighlight();
12959     SetGameInfo();
12960     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12961     DisplayTitle(buf);
12962     if (first.sendName) {
12963       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12964       SendToProgram(buf, &first);
12965     }
12966     if (first.sendTime) {
12967       if (first.useColors) {
12968         SendToProgram("black\n", &first); /*gnu kludge*/
12969       }
12970       SendTimeRemaining(&first, TRUE);
12971     }
12972     if (first.useColors) {
12973       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12974     }
12975     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12976     SetMachineThinkingEnables();
12977     first.maybeThinking = TRUE;
12978     StartClocks();
12979     firstMove = FALSE;
12980
12981     if (appData.autoFlipView && !flipView) {
12982       flipView = !flipView;
12983       DrawPosition(FALSE, NULL);
12984       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12985     }
12986
12987     if(bookHit) { // [HGM] book: simulate book reply
12988         static char bookMove[MSG_SIZ]; // a bit generous?
12989
12990         programStats.nodes = programStats.depth = programStats.time =
12991         programStats.score = programStats.got_only_move = 0;
12992         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12993
12994         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12995         strcat(bookMove, bookHit);
12996         HandleMachineMove(bookMove, &first);
12997     }
12998 }
12999
13000 void
13001 MachineBlackEvent()
13002 {
13003   char buf[MSG_SIZ];
13004   char *bookHit = NULL;
13005
13006     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13007         return;
13008
13009
13010     if (gameMode == PlayFromGameFile ||
13011         gameMode == TwoMachinesPlay  ||
13012         gameMode == Training         ||
13013         gameMode == AnalyzeMode      ||
13014         gameMode == EndOfGame)
13015         EditGameEvent();
13016
13017     if (gameMode == EditPosition)
13018         EditPositionDone(TRUE);
13019
13020     if (WhiteOnMove(currentMove)) {
13021         DisplayError(_("It is not Black's turn"), 0);
13022         return;
13023     }
13024
13025     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13026       ExitAnalyzeMode();
13027
13028     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13029         gameMode == AnalyzeFile)
13030         TruncateGame();
13031
13032     ResurrectChessProgram();    /* in case it isn't running */
13033     gameMode = MachinePlaysBlack;
13034     pausing = FALSE;
13035     ModeHighlight();
13036     SetGameInfo();
13037     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13038     DisplayTitle(buf);
13039     if (first.sendName) {
13040       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13041       SendToProgram(buf, &first);
13042     }
13043     if (first.sendTime) {
13044       if (first.useColors) {
13045         SendToProgram("white\n", &first); /*gnu kludge*/
13046       }
13047       SendTimeRemaining(&first, FALSE);
13048     }
13049     if (first.useColors) {
13050       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13051     }
13052     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13053     SetMachineThinkingEnables();
13054     first.maybeThinking = TRUE;
13055     StartClocks();
13056
13057     if (appData.autoFlipView && flipView) {
13058       flipView = !flipView;
13059       DrawPosition(FALSE, NULL);
13060       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13061     }
13062     if(bookHit) { // [HGM] book: simulate book reply
13063         static char bookMove[MSG_SIZ]; // a bit generous?
13064
13065         programStats.nodes = programStats.depth = programStats.time =
13066         programStats.score = programStats.got_only_move = 0;
13067         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13068
13069         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13070         strcat(bookMove, bookHit);
13071         HandleMachineMove(bookMove, &first);
13072     }
13073 }
13074
13075
13076 void
13077 DisplayTwoMachinesTitle()
13078 {
13079     char buf[MSG_SIZ];
13080     if (appData.matchGames > 0) {
13081         if(appData.tourneyFile[0]) {
13082           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13083                    gameInfo.white, gameInfo.black,
13084                    nextGame+1, appData.matchGames+1,
13085                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13086         } else 
13087         if (first.twoMachinesColor[0] == 'w') {
13088           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13089                    gameInfo.white, gameInfo.black,
13090                    first.matchWins, second.matchWins,
13091                    matchGame - 1 - (first.matchWins + second.matchWins));
13092         } else {
13093           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13094                    gameInfo.white, gameInfo.black,
13095                    second.matchWins, first.matchWins,
13096                    matchGame - 1 - (first.matchWins + second.matchWins));
13097         }
13098     } else {
13099       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13100     }
13101     DisplayTitle(buf);
13102 }
13103
13104 void
13105 SettingsMenuIfReady()
13106 {
13107   if (second.lastPing != second.lastPong) {
13108     DisplayMessage("", _("Waiting for second chess program"));
13109     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13110     return;
13111   }
13112   ThawUI();
13113   DisplayMessage("", "");
13114   SettingsPopUp(&second);
13115 }
13116
13117 int
13118 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13119 {
13120     char buf[MSG_SIZ];
13121     if (cps->pr == NULL) {
13122         StartChessProgram(cps);
13123         if (cps->protocolVersion == 1) {
13124           retry();
13125         } else {
13126           /* kludge: allow timeout for initial "feature" command */
13127           FreezeUI();
13128           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13129           DisplayMessage("", buf);
13130           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13131         }
13132         return 1;
13133     }
13134     return 0;
13135 }
13136
13137 void
13138 TwoMachinesEvent P((void))
13139 {
13140     int i;
13141     char buf[MSG_SIZ];
13142     ChessProgramState *onmove;
13143     char *bookHit = NULL;
13144     static int stalling = 0;
13145     TimeMark now;
13146     long wait;
13147
13148     if (appData.noChessProgram) return;
13149
13150     switch (gameMode) {
13151       case TwoMachinesPlay:
13152         return;
13153       case MachinePlaysWhite:
13154       case MachinePlaysBlack:
13155         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13156             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13157             return;
13158         }
13159         /* fall through */
13160       case BeginningOfGame:
13161       case PlayFromGameFile:
13162       case EndOfGame:
13163         EditGameEvent();
13164         if (gameMode != EditGame) return;
13165         break;
13166       case EditPosition:
13167         EditPositionDone(TRUE);
13168         break;
13169       case AnalyzeMode:
13170       case AnalyzeFile:
13171         ExitAnalyzeMode();
13172         break;
13173       case EditGame:
13174       default:
13175         break;
13176     }
13177
13178 //    forwardMostMove = currentMove;
13179     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13180
13181     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13182
13183     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13184     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13185       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13186       return;
13187     }
13188     if(!stalling) {
13189       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13190       SendToProgram("force\n", &second);
13191       stalling = 1;
13192       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13193       return;
13194     }
13195     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13196     if(appData.matchPause>10000 || appData.matchPause<10)
13197                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13198     wait = SubtractTimeMarks(&now, &pauseStart);
13199     if(wait < appData.matchPause) {
13200         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13201         return;
13202     }
13203     stalling = 0;
13204     DisplayMessage("", "");
13205     if (startedFromSetupPosition) {
13206         SendBoard(&second, backwardMostMove);
13207     if (appData.debugMode) {
13208         fprintf(debugFP, "Two Machines\n");
13209     }
13210     }
13211     for (i = backwardMostMove; i < forwardMostMove; i++) {
13212         SendMoveToProgram(i, &second);
13213     }
13214
13215     gameMode = TwoMachinesPlay;
13216     pausing = FALSE;
13217     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13218     SetGameInfo();
13219     DisplayTwoMachinesTitle();
13220     firstMove = TRUE;
13221     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13222         onmove = &first;
13223     } else {
13224         onmove = &second;
13225     }
13226     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13227     SendToProgram(first.computerString, &first);
13228     if (first.sendName) {
13229       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13230       SendToProgram(buf, &first);
13231     }
13232     SendToProgram(second.computerString, &second);
13233     if (second.sendName) {
13234       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13235       SendToProgram(buf, &second);
13236     }
13237
13238     ResetClocks();
13239     if (!first.sendTime || !second.sendTime) {
13240         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13241         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13242     }
13243     if (onmove->sendTime) {
13244       if (onmove->useColors) {
13245         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13246       }
13247       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13248     }
13249     if (onmove->useColors) {
13250       SendToProgram(onmove->twoMachinesColor, onmove);
13251     }
13252     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13253 //    SendToProgram("go\n", onmove);
13254     onmove->maybeThinking = TRUE;
13255     SetMachineThinkingEnables();
13256
13257     StartClocks();
13258
13259     if(bookHit) { // [HGM] book: simulate book reply
13260         static char bookMove[MSG_SIZ]; // a bit generous?
13261
13262         programStats.nodes = programStats.depth = programStats.time =
13263         programStats.score = programStats.got_only_move = 0;
13264         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13265
13266         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13267         strcat(bookMove, bookHit);
13268         savedMessage = bookMove; // args for deferred call
13269         savedState = onmove;
13270         ScheduleDelayedEvent(DeferredBookMove, 1);
13271     }
13272 }
13273
13274 void
13275 TrainingEvent()
13276 {
13277     if (gameMode == Training) {
13278       SetTrainingModeOff();
13279       gameMode = PlayFromGameFile;
13280       DisplayMessage("", _("Training mode off"));
13281     } else {
13282       gameMode = Training;
13283       animateTraining = appData.animate;
13284
13285       /* make sure we are not already at the end of the game */
13286       if (currentMove < forwardMostMove) {
13287         SetTrainingModeOn();
13288         DisplayMessage("", _("Training mode on"));
13289       } else {
13290         gameMode = PlayFromGameFile;
13291         DisplayError(_("Already at end of game"), 0);
13292       }
13293     }
13294     ModeHighlight();
13295 }
13296
13297 void
13298 IcsClientEvent()
13299 {
13300     if (!appData.icsActive) return;
13301     switch (gameMode) {
13302       case IcsPlayingWhite:
13303       case IcsPlayingBlack:
13304       case IcsObserving:
13305       case IcsIdle:
13306       case BeginningOfGame:
13307       case IcsExamining:
13308         return;
13309
13310       case EditGame:
13311         break;
13312
13313       case EditPosition:
13314         EditPositionDone(TRUE);
13315         break;
13316
13317       case AnalyzeMode:
13318       case AnalyzeFile:
13319         ExitAnalyzeMode();
13320         break;
13321
13322       default:
13323         EditGameEvent();
13324         break;
13325     }
13326
13327     gameMode = IcsIdle;
13328     ModeHighlight();
13329     return;
13330 }
13331
13332
13333 void
13334 EditGameEvent()
13335 {
13336     int i;
13337
13338     switch (gameMode) {
13339       case Training:
13340         SetTrainingModeOff();
13341         break;
13342       case MachinePlaysWhite:
13343       case MachinePlaysBlack:
13344       case BeginningOfGame:
13345         SendToProgram("force\n", &first);
13346         SetUserThinkingEnables();
13347         break;
13348       case PlayFromGameFile:
13349         (void) StopLoadGameTimer();
13350         if (gameFileFP != NULL) {
13351             gameFileFP = NULL;
13352         }
13353         break;
13354       case EditPosition:
13355         EditPositionDone(TRUE);
13356         break;
13357       case AnalyzeMode:
13358       case AnalyzeFile:
13359         ExitAnalyzeMode();
13360         SendToProgram("force\n", &first);
13361         break;
13362       case TwoMachinesPlay:
13363         GameEnds(EndOfFile, NULL, GE_PLAYER);
13364         ResurrectChessProgram();
13365         SetUserThinkingEnables();
13366         break;
13367       case EndOfGame:
13368         ResurrectChessProgram();
13369         break;
13370       case IcsPlayingBlack:
13371       case IcsPlayingWhite:
13372         DisplayError(_("Warning: You are still playing a game"), 0);
13373         break;
13374       case IcsObserving:
13375         DisplayError(_("Warning: You are still observing a game"), 0);
13376         break;
13377       case IcsExamining:
13378         DisplayError(_("Warning: You are still examining a game"), 0);
13379         break;
13380       case IcsIdle:
13381         break;
13382       case EditGame:
13383       default:
13384         return;
13385     }
13386
13387     pausing = FALSE;
13388     StopClocks();
13389     first.offeredDraw = second.offeredDraw = 0;
13390
13391     if (gameMode == PlayFromGameFile) {
13392         whiteTimeRemaining = timeRemaining[0][currentMove];
13393         blackTimeRemaining = timeRemaining[1][currentMove];
13394         DisplayTitle("");
13395     }
13396
13397     if (gameMode == MachinePlaysWhite ||
13398         gameMode == MachinePlaysBlack ||
13399         gameMode == TwoMachinesPlay ||
13400         gameMode == EndOfGame) {
13401         i = forwardMostMove;
13402         while (i > currentMove) {
13403             SendToProgram("undo\n", &first);
13404             i--;
13405         }
13406         whiteTimeRemaining = timeRemaining[0][currentMove];
13407         blackTimeRemaining = timeRemaining[1][currentMove];
13408         DisplayBothClocks();
13409         if (whiteFlag || blackFlag) {
13410             whiteFlag = blackFlag = 0;
13411         }
13412         DisplayTitle("");
13413     }
13414
13415     gameMode = EditGame;
13416     ModeHighlight();
13417     SetGameInfo();
13418 }
13419
13420
13421 void
13422 EditPositionEvent()
13423 {
13424     if (gameMode == EditPosition) {
13425         EditGameEvent();
13426         return;
13427     }
13428
13429     EditGameEvent();
13430     if (gameMode != EditGame) return;
13431
13432     gameMode = EditPosition;
13433     ModeHighlight();
13434     SetGameInfo();
13435     if (currentMove > 0)
13436       CopyBoard(boards[0], boards[currentMove]);
13437
13438     blackPlaysFirst = !WhiteOnMove(currentMove);
13439     ResetClocks();
13440     currentMove = forwardMostMove = backwardMostMove = 0;
13441     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13442     DisplayMove(-1);
13443 }
13444
13445 void
13446 ExitAnalyzeMode()
13447 {
13448     /* [DM] icsEngineAnalyze - possible call from other functions */
13449     if (appData.icsEngineAnalyze) {
13450         appData.icsEngineAnalyze = FALSE;
13451
13452         DisplayMessage("",_("Close ICS engine analyze..."));
13453     }
13454     if (first.analysisSupport && first.analyzing) {
13455       SendToProgram("exit\n", &first);
13456       first.analyzing = FALSE;
13457     }
13458     thinkOutput[0] = NULLCHAR;
13459 }
13460
13461 void
13462 EditPositionDone(Boolean fakeRights)
13463 {
13464     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13465
13466     startedFromSetupPosition = TRUE;
13467     InitChessProgram(&first, FALSE);
13468     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13469       boards[0][EP_STATUS] = EP_NONE;
13470       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13471     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13472         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13473         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13474       } else boards[0][CASTLING][2] = NoRights;
13475     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13476         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13477         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13478       } else boards[0][CASTLING][5] = NoRights;
13479     }
13480     SendToProgram("force\n", &first);
13481     if (blackPlaysFirst) {
13482         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13483         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13484         currentMove = forwardMostMove = backwardMostMove = 1;
13485         CopyBoard(boards[1], boards[0]);
13486     } else {
13487         currentMove = forwardMostMove = backwardMostMove = 0;
13488     }
13489     SendBoard(&first, forwardMostMove);
13490     if (appData.debugMode) {
13491         fprintf(debugFP, "EditPosDone\n");
13492     }
13493     DisplayTitle("");
13494     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13495     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13496     gameMode = EditGame;
13497     ModeHighlight();
13498     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13499     ClearHighlights(); /* [AS] */
13500 }
13501
13502 /* Pause for `ms' milliseconds */
13503 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13504 void
13505 TimeDelay(ms)
13506      long ms;
13507 {
13508     TimeMark m1, m2;
13509
13510     GetTimeMark(&m1);
13511     do {
13512         GetTimeMark(&m2);
13513     } while (SubtractTimeMarks(&m2, &m1) < ms);
13514 }
13515
13516 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13517 void
13518 SendMultiLineToICS(buf)
13519      char *buf;
13520 {
13521     char temp[MSG_SIZ+1], *p;
13522     int len;
13523
13524     len = strlen(buf);
13525     if (len > MSG_SIZ)
13526       len = MSG_SIZ;
13527
13528     strncpy(temp, buf, len);
13529     temp[len] = 0;
13530
13531     p = temp;
13532     while (*p) {
13533         if (*p == '\n' || *p == '\r')
13534           *p = ' ';
13535         ++p;
13536     }
13537
13538     strcat(temp, "\n");
13539     SendToICS(temp);
13540     SendToPlayer(temp, strlen(temp));
13541 }
13542
13543 void
13544 SetWhiteToPlayEvent()
13545 {
13546     if (gameMode == EditPosition) {
13547         blackPlaysFirst = FALSE;
13548         DisplayBothClocks();    /* works because currentMove is 0 */
13549     } else if (gameMode == IcsExamining) {
13550         SendToICS(ics_prefix);
13551         SendToICS("tomove white\n");
13552     }
13553 }
13554
13555 void
13556 SetBlackToPlayEvent()
13557 {
13558     if (gameMode == EditPosition) {
13559         blackPlaysFirst = TRUE;
13560         currentMove = 1;        /* kludge */
13561         DisplayBothClocks();
13562         currentMove = 0;
13563     } else if (gameMode == IcsExamining) {
13564         SendToICS(ics_prefix);
13565         SendToICS("tomove black\n");
13566     }
13567 }
13568
13569 void
13570 EditPositionMenuEvent(selection, x, y)
13571      ChessSquare selection;
13572      int x, y;
13573 {
13574     char buf[MSG_SIZ];
13575     ChessSquare piece = boards[0][y][x];
13576
13577     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13578
13579     switch (selection) {
13580       case ClearBoard:
13581         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13582             SendToICS(ics_prefix);
13583             SendToICS("bsetup clear\n");
13584         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13585             SendToICS(ics_prefix);
13586             SendToICS("clearboard\n");
13587         } else {
13588             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13589                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13590                 for (y = 0; y < BOARD_HEIGHT; y++) {
13591                     if (gameMode == IcsExamining) {
13592                         if (boards[currentMove][y][x] != EmptySquare) {
13593                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13594                                     AAA + x, ONE + y);
13595                             SendToICS(buf);
13596                         }
13597                     } else {
13598                         boards[0][y][x] = p;
13599                     }
13600                 }
13601             }
13602         }
13603         if (gameMode == EditPosition) {
13604             DrawPosition(FALSE, boards[0]);
13605         }
13606         break;
13607
13608       case WhitePlay:
13609         SetWhiteToPlayEvent();
13610         break;
13611
13612       case BlackPlay:
13613         SetBlackToPlayEvent();
13614         break;
13615
13616       case EmptySquare:
13617         if (gameMode == IcsExamining) {
13618             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13619             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13620             SendToICS(buf);
13621         } else {
13622             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13623                 if(x == BOARD_LEFT-2) {
13624                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13625                     boards[0][y][1] = 0;
13626                 } else
13627                 if(x == BOARD_RGHT+1) {
13628                     if(y >= gameInfo.holdingsSize) break;
13629                     boards[0][y][BOARD_WIDTH-2] = 0;
13630                 } else break;
13631             }
13632             boards[0][y][x] = EmptySquare;
13633             DrawPosition(FALSE, boards[0]);
13634         }
13635         break;
13636
13637       case PromotePiece:
13638         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13639            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13640             selection = (ChessSquare) (PROMOTED piece);
13641         } else if(piece == EmptySquare) selection = WhiteSilver;
13642         else selection = (ChessSquare)((int)piece - 1);
13643         goto defaultlabel;
13644
13645       case DemotePiece:
13646         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13647            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13648             selection = (ChessSquare) (DEMOTED piece);
13649         } else if(piece == EmptySquare) selection = BlackSilver;
13650         else selection = (ChessSquare)((int)piece + 1);
13651         goto defaultlabel;
13652
13653       case WhiteQueen:
13654       case BlackQueen:
13655         if(gameInfo.variant == VariantShatranj ||
13656            gameInfo.variant == VariantXiangqi  ||
13657            gameInfo.variant == VariantCourier  ||
13658            gameInfo.variant == VariantMakruk     )
13659             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13660         goto defaultlabel;
13661
13662       case WhiteKing:
13663       case BlackKing:
13664         if(gameInfo.variant == VariantXiangqi)
13665             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13666         if(gameInfo.variant == VariantKnightmate)
13667             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13668       default:
13669         defaultlabel:
13670         if (gameMode == IcsExamining) {
13671             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13672             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13673                      PieceToChar(selection), AAA + x, ONE + y);
13674             SendToICS(buf);
13675         } else {
13676             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13677                 int n;
13678                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13679                     n = PieceToNumber(selection - BlackPawn);
13680                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13681                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13682                     boards[0][BOARD_HEIGHT-1-n][1]++;
13683                 } else
13684                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13685                     n = PieceToNumber(selection);
13686                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13687                     boards[0][n][BOARD_WIDTH-1] = selection;
13688                     boards[0][n][BOARD_WIDTH-2]++;
13689                 }
13690             } else
13691             boards[0][y][x] = selection;
13692             DrawPosition(TRUE, boards[0]);
13693         }
13694         break;
13695     }
13696 }
13697
13698
13699 void
13700 DropMenuEvent(selection, x, y)
13701      ChessSquare selection;
13702      int x, y;
13703 {
13704     ChessMove moveType;
13705
13706     switch (gameMode) {
13707       case IcsPlayingWhite:
13708       case MachinePlaysBlack:
13709         if (!WhiteOnMove(currentMove)) {
13710             DisplayMoveError(_("It is Black's turn"));
13711             return;
13712         }
13713         moveType = WhiteDrop;
13714         break;
13715       case IcsPlayingBlack:
13716       case MachinePlaysWhite:
13717         if (WhiteOnMove(currentMove)) {
13718             DisplayMoveError(_("It is White's turn"));
13719             return;
13720         }
13721         moveType = BlackDrop;
13722         break;
13723       case EditGame:
13724         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13725         break;
13726       default:
13727         return;
13728     }
13729
13730     if (moveType == BlackDrop && selection < BlackPawn) {
13731       selection = (ChessSquare) ((int) selection
13732                                  + (int) BlackPawn - (int) WhitePawn);
13733     }
13734     if (boards[currentMove][y][x] != EmptySquare) {
13735         DisplayMoveError(_("That square is occupied"));
13736         return;
13737     }
13738
13739     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13740 }
13741
13742 void
13743 AcceptEvent()
13744 {
13745     /* Accept a pending offer of any kind from opponent */
13746
13747     if (appData.icsActive) {
13748         SendToICS(ics_prefix);
13749         SendToICS("accept\n");
13750     } else if (cmailMsgLoaded) {
13751         if (currentMove == cmailOldMove &&
13752             commentList[cmailOldMove] != NULL &&
13753             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13754                    "Black offers a draw" : "White offers a draw")) {
13755             TruncateGame();
13756             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13757             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13758         } else {
13759             DisplayError(_("There is no pending offer on this move"), 0);
13760             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13761         }
13762     } else {
13763         /* Not used for offers from chess program */
13764     }
13765 }
13766
13767 void
13768 DeclineEvent()
13769 {
13770     /* Decline a pending offer of any kind from opponent */
13771
13772     if (appData.icsActive) {
13773         SendToICS(ics_prefix);
13774         SendToICS("decline\n");
13775     } else if (cmailMsgLoaded) {
13776         if (currentMove == cmailOldMove &&
13777             commentList[cmailOldMove] != NULL &&
13778             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13779                    "Black offers a draw" : "White offers a draw")) {
13780 #ifdef NOTDEF
13781             AppendComment(cmailOldMove, "Draw declined", TRUE);
13782             DisplayComment(cmailOldMove - 1, "Draw declined");
13783 #endif /*NOTDEF*/
13784         } else {
13785             DisplayError(_("There is no pending offer on this move"), 0);
13786         }
13787     } else {
13788         /* Not used for offers from chess program */
13789     }
13790 }
13791
13792 void
13793 RematchEvent()
13794 {
13795     /* Issue ICS rematch command */
13796     if (appData.icsActive) {
13797         SendToICS(ics_prefix);
13798         SendToICS("rematch\n");
13799     }
13800 }
13801
13802 void
13803 CallFlagEvent()
13804 {
13805     /* Call your opponent's flag (claim a win on time) */
13806     if (appData.icsActive) {
13807         SendToICS(ics_prefix);
13808         SendToICS("flag\n");
13809     } else {
13810         switch (gameMode) {
13811           default:
13812             return;
13813           case MachinePlaysWhite:
13814             if (whiteFlag) {
13815                 if (blackFlag)
13816                   GameEnds(GameIsDrawn, "Both players ran out of time",
13817                            GE_PLAYER);
13818                 else
13819                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13820             } else {
13821                 DisplayError(_("Your opponent is not out of time"), 0);
13822             }
13823             break;
13824           case MachinePlaysBlack:
13825             if (blackFlag) {
13826                 if (whiteFlag)
13827                   GameEnds(GameIsDrawn, "Both players ran out of time",
13828                            GE_PLAYER);
13829                 else
13830                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13831             } else {
13832                 DisplayError(_("Your opponent is not out of time"), 0);
13833             }
13834             break;
13835         }
13836     }
13837 }
13838
13839 void
13840 ClockClick(int which)
13841 {       // [HGM] code moved to back-end from winboard.c
13842         if(which) { // black clock
13843           if (gameMode == EditPosition || gameMode == IcsExamining) {
13844             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13845             SetBlackToPlayEvent();
13846           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13847           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13848           } else if (shiftKey) {
13849             AdjustClock(which, -1);
13850           } else if (gameMode == IcsPlayingWhite ||
13851                      gameMode == MachinePlaysBlack) {
13852             CallFlagEvent();
13853           }
13854         } else { // white clock
13855           if (gameMode == EditPosition || gameMode == IcsExamining) {
13856             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13857             SetWhiteToPlayEvent();
13858           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13859           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13860           } else if (shiftKey) {
13861             AdjustClock(which, -1);
13862           } else if (gameMode == IcsPlayingBlack ||
13863                    gameMode == MachinePlaysWhite) {
13864             CallFlagEvent();
13865           }
13866         }
13867 }
13868
13869 void
13870 DrawEvent()
13871 {
13872     /* Offer draw or accept pending draw offer from opponent */
13873
13874     if (appData.icsActive) {
13875         /* Note: tournament rules require draw offers to be
13876            made after you make your move but before you punch
13877            your clock.  Currently ICS doesn't let you do that;
13878            instead, you immediately punch your clock after making
13879            a move, but you can offer a draw at any time. */
13880
13881         SendToICS(ics_prefix);
13882         SendToICS("draw\n");
13883         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13884     } else if (cmailMsgLoaded) {
13885         if (currentMove == cmailOldMove &&
13886             commentList[cmailOldMove] != NULL &&
13887             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13888                    "Black offers a draw" : "White offers a draw")) {
13889             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13890             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13891         } else if (currentMove == cmailOldMove + 1) {
13892             char *offer = WhiteOnMove(cmailOldMove) ?
13893               "White offers a draw" : "Black offers a draw";
13894             AppendComment(currentMove, offer, TRUE);
13895             DisplayComment(currentMove - 1, offer);
13896             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13897         } else {
13898             DisplayError(_("You must make your move before offering a draw"), 0);
13899             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13900         }
13901     } else if (first.offeredDraw) {
13902         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13903     } else {
13904         if (first.sendDrawOffers) {
13905             SendToProgram("draw\n", &first);
13906             userOfferedDraw = TRUE;
13907         }
13908     }
13909 }
13910
13911 void
13912 AdjournEvent()
13913 {
13914     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13915
13916     if (appData.icsActive) {
13917         SendToICS(ics_prefix);
13918         SendToICS("adjourn\n");
13919     } else {
13920         /* Currently GNU Chess doesn't offer or accept Adjourns */
13921     }
13922 }
13923
13924
13925 void
13926 AbortEvent()
13927 {
13928     /* Offer Abort or accept pending Abort offer from opponent */
13929
13930     if (appData.icsActive) {
13931         SendToICS(ics_prefix);
13932         SendToICS("abort\n");
13933     } else {
13934         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13935     }
13936 }
13937
13938 void
13939 ResignEvent()
13940 {
13941     /* Resign.  You can do this even if it's not your turn. */
13942
13943     if (appData.icsActive) {
13944         SendToICS(ics_prefix);
13945         SendToICS("resign\n");
13946     } else {
13947         switch (gameMode) {
13948           case MachinePlaysWhite:
13949             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13950             break;
13951           case MachinePlaysBlack:
13952             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13953             break;
13954           case EditGame:
13955             if (cmailMsgLoaded) {
13956                 TruncateGame();
13957                 if (WhiteOnMove(cmailOldMove)) {
13958                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13959                 } else {
13960                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13961                 }
13962                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13963             }
13964             break;
13965           default:
13966             break;
13967         }
13968     }
13969 }
13970
13971
13972 void
13973 StopObservingEvent()
13974 {
13975     /* Stop observing current games */
13976     SendToICS(ics_prefix);
13977     SendToICS("unobserve\n");
13978 }
13979
13980 void
13981 StopExaminingEvent()
13982 {
13983     /* Stop observing current game */
13984     SendToICS(ics_prefix);
13985     SendToICS("unexamine\n");
13986 }
13987
13988 void
13989 ForwardInner(target)
13990      int target;
13991 {
13992     int limit;
13993
13994     if (appData.debugMode)
13995         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13996                 target, currentMove, forwardMostMove);
13997
13998     if (gameMode == EditPosition)
13999       return;
14000
14001     if (gameMode == PlayFromGameFile && !pausing)
14002       PauseEvent();
14003
14004     if (gameMode == IcsExamining && pausing)
14005       limit = pauseExamForwardMostMove;
14006     else
14007       limit = forwardMostMove;
14008
14009     if (target > limit) target = limit;
14010
14011     if (target > 0 && moveList[target - 1][0]) {
14012         int fromX, fromY, toX, toY;
14013         toX = moveList[target - 1][2] - AAA;
14014         toY = moveList[target - 1][3] - ONE;
14015         if (moveList[target - 1][1] == '@') {
14016             if (appData.highlightLastMove) {
14017                 SetHighlights(-1, -1, toX, toY);
14018             }
14019         } else {
14020             fromX = moveList[target - 1][0] - AAA;
14021             fromY = moveList[target - 1][1] - ONE;
14022             if (target == currentMove + 1) {
14023                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14024             }
14025             if (appData.highlightLastMove) {
14026                 SetHighlights(fromX, fromY, toX, toY);
14027             }
14028         }
14029     }
14030     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14031         gameMode == Training || gameMode == PlayFromGameFile ||
14032         gameMode == AnalyzeFile) {
14033         while (currentMove < target) {
14034             SendMoveToProgram(currentMove++, &first);
14035         }
14036     } else {
14037         currentMove = target;
14038     }
14039
14040     if (gameMode == EditGame || gameMode == EndOfGame) {
14041         whiteTimeRemaining = timeRemaining[0][currentMove];
14042         blackTimeRemaining = timeRemaining[1][currentMove];
14043     }
14044     DisplayBothClocks();
14045     DisplayMove(currentMove - 1);
14046     DrawPosition(FALSE, boards[currentMove]);
14047     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14048     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14049         DisplayComment(currentMove - 1, commentList[currentMove]);
14050     }
14051     DisplayBook(currentMove);
14052 }
14053
14054
14055 void
14056 ForwardEvent()
14057 {
14058     if (gameMode == IcsExamining && !pausing) {
14059         SendToICS(ics_prefix);
14060         SendToICS("forward\n");
14061     } else {
14062         ForwardInner(currentMove + 1);
14063     }
14064 }
14065
14066 void
14067 ToEndEvent()
14068 {
14069     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14070         /* to optimze, we temporarily turn off analysis mode while we feed
14071          * the remaining moves to the engine. Otherwise we get analysis output
14072          * after each move.
14073          */
14074         if (first.analysisSupport) {
14075           SendToProgram("exit\nforce\n", &first);
14076           first.analyzing = FALSE;
14077         }
14078     }
14079
14080     if (gameMode == IcsExamining && !pausing) {
14081         SendToICS(ics_prefix);
14082         SendToICS("forward 999999\n");
14083     } else {
14084         ForwardInner(forwardMostMove);
14085     }
14086
14087     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14088         /* we have fed all the moves, so reactivate analysis mode */
14089         SendToProgram("analyze\n", &first);
14090         first.analyzing = TRUE;
14091         /*first.maybeThinking = TRUE;*/
14092         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14093     }
14094 }
14095
14096 void
14097 BackwardInner(target)
14098      int target;
14099 {
14100     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14101
14102     if (appData.debugMode)
14103         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14104                 target, currentMove, forwardMostMove);
14105
14106     if (gameMode == EditPosition) return;
14107     if (currentMove <= backwardMostMove) {
14108         ClearHighlights();
14109         DrawPosition(full_redraw, boards[currentMove]);
14110         return;
14111     }
14112     if (gameMode == PlayFromGameFile && !pausing)
14113       PauseEvent();
14114
14115     if (moveList[target][0]) {
14116         int fromX, fromY, toX, toY;
14117         toX = moveList[target][2] - AAA;
14118         toY = moveList[target][3] - ONE;
14119         if (moveList[target][1] == '@') {
14120             if (appData.highlightLastMove) {
14121                 SetHighlights(-1, -1, toX, toY);
14122             }
14123         } else {
14124             fromX = moveList[target][0] - AAA;
14125             fromY = moveList[target][1] - ONE;
14126             if (target == currentMove - 1) {
14127                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14128             }
14129             if (appData.highlightLastMove) {
14130                 SetHighlights(fromX, fromY, toX, toY);
14131             }
14132         }
14133     }
14134     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14135         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14136         while (currentMove > target) {
14137             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14138                 // null move cannot be undone. Reload program with move history before it.
14139                 int i;
14140                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14141                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14142                 }
14143                 SendBoard(&first, i); 
14144                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14145                 break;
14146             }
14147             SendToProgram("undo\n", &first);
14148             currentMove--;
14149         }
14150     } else {
14151         currentMove = target;
14152     }
14153
14154     if (gameMode == EditGame || gameMode == EndOfGame) {
14155         whiteTimeRemaining = timeRemaining[0][currentMove];
14156         blackTimeRemaining = timeRemaining[1][currentMove];
14157     }
14158     DisplayBothClocks();
14159     DisplayMove(currentMove - 1);
14160     DrawPosition(full_redraw, boards[currentMove]);
14161     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14162     // [HGM] PV info: routine tests if comment empty
14163     DisplayComment(currentMove - 1, commentList[currentMove]);
14164     DisplayBook(currentMove);
14165 }
14166
14167 void
14168 BackwardEvent()
14169 {
14170     if (gameMode == IcsExamining && !pausing) {
14171         SendToICS(ics_prefix);
14172         SendToICS("backward\n");
14173     } else {
14174         BackwardInner(currentMove - 1);
14175     }
14176 }
14177
14178 void
14179 ToStartEvent()
14180 {
14181     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14182         /* to optimize, we temporarily turn off analysis mode while we undo
14183          * all the moves. Otherwise we get analysis output after each undo.
14184          */
14185         if (first.analysisSupport) {
14186           SendToProgram("exit\nforce\n", &first);
14187           first.analyzing = FALSE;
14188         }
14189     }
14190
14191     if (gameMode == IcsExamining && !pausing) {
14192         SendToICS(ics_prefix);
14193         SendToICS("backward 999999\n");
14194     } else {
14195         BackwardInner(backwardMostMove);
14196     }
14197
14198     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14199         /* we have fed all the moves, so reactivate analysis mode */
14200         SendToProgram("analyze\n", &first);
14201         first.analyzing = TRUE;
14202         /*first.maybeThinking = TRUE;*/
14203         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14204     }
14205 }
14206
14207 void
14208 ToNrEvent(int to)
14209 {
14210   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14211   if (to >= forwardMostMove) to = forwardMostMove;
14212   if (to <= backwardMostMove) to = backwardMostMove;
14213   if (to < currentMove) {
14214     BackwardInner(to);
14215   } else {
14216     ForwardInner(to);
14217   }
14218 }
14219
14220 void
14221 RevertEvent(Boolean annotate)
14222 {
14223     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14224         return;
14225     }
14226     if (gameMode != IcsExamining) {
14227         DisplayError(_("You are not examining a game"), 0);
14228         return;
14229     }
14230     if (pausing) {
14231         DisplayError(_("You can't revert while pausing"), 0);
14232         return;
14233     }
14234     SendToICS(ics_prefix);
14235     SendToICS("revert\n");
14236 }
14237
14238 void
14239 RetractMoveEvent()
14240 {
14241     switch (gameMode) {
14242       case MachinePlaysWhite:
14243       case MachinePlaysBlack:
14244         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14245             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14246             return;
14247         }
14248         if (forwardMostMove < 2) return;
14249         currentMove = forwardMostMove = forwardMostMove - 2;
14250         whiteTimeRemaining = timeRemaining[0][currentMove];
14251         blackTimeRemaining = timeRemaining[1][currentMove];
14252         DisplayBothClocks();
14253         DisplayMove(currentMove - 1);
14254         ClearHighlights();/*!! could figure this out*/
14255         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14256         SendToProgram("remove\n", &first);
14257         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14258         break;
14259
14260       case BeginningOfGame:
14261       default:
14262         break;
14263
14264       case IcsPlayingWhite:
14265       case IcsPlayingBlack:
14266         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14267             SendToICS(ics_prefix);
14268             SendToICS("takeback 2\n");
14269         } else {
14270             SendToICS(ics_prefix);
14271             SendToICS("takeback 1\n");
14272         }
14273         break;
14274     }
14275 }
14276
14277 void
14278 MoveNowEvent()
14279 {
14280     ChessProgramState *cps;
14281
14282     switch (gameMode) {
14283       case MachinePlaysWhite:
14284         if (!WhiteOnMove(forwardMostMove)) {
14285             DisplayError(_("It is your turn"), 0);
14286             return;
14287         }
14288         cps = &first;
14289         break;
14290       case MachinePlaysBlack:
14291         if (WhiteOnMove(forwardMostMove)) {
14292             DisplayError(_("It is your turn"), 0);
14293             return;
14294         }
14295         cps = &first;
14296         break;
14297       case TwoMachinesPlay:
14298         if (WhiteOnMove(forwardMostMove) ==
14299             (first.twoMachinesColor[0] == 'w')) {
14300             cps = &first;
14301         } else {
14302             cps = &second;
14303         }
14304         break;
14305       case BeginningOfGame:
14306       default:
14307         return;
14308     }
14309     SendToProgram("?\n", cps);
14310 }
14311
14312 void
14313 TruncateGameEvent()
14314 {
14315     EditGameEvent();
14316     if (gameMode != EditGame) return;
14317     TruncateGame();
14318 }
14319
14320 void
14321 TruncateGame()
14322 {
14323     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14324     if (forwardMostMove > currentMove) {
14325         if (gameInfo.resultDetails != NULL) {
14326             free(gameInfo.resultDetails);
14327             gameInfo.resultDetails = NULL;
14328             gameInfo.result = GameUnfinished;
14329         }
14330         forwardMostMove = currentMove;
14331         HistorySet(parseList, backwardMostMove, forwardMostMove,
14332                    currentMove-1);
14333     }
14334 }
14335
14336 void
14337 HintEvent()
14338 {
14339     if (appData.noChessProgram) return;
14340     switch (gameMode) {
14341       case MachinePlaysWhite:
14342         if (WhiteOnMove(forwardMostMove)) {
14343             DisplayError(_("Wait until your turn"), 0);
14344             return;
14345         }
14346         break;
14347       case BeginningOfGame:
14348       case MachinePlaysBlack:
14349         if (!WhiteOnMove(forwardMostMove)) {
14350             DisplayError(_("Wait until your turn"), 0);
14351             return;
14352         }
14353         break;
14354       default:
14355         DisplayError(_("No hint available"), 0);
14356         return;
14357     }
14358     SendToProgram("hint\n", &first);
14359     hintRequested = TRUE;
14360 }
14361
14362 void
14363 BookEvent()
14364 {
14365     if (appData.noChessProgram) return;
14366     switch (gameMode) {
14367       case MachinePlaysWhite:
14368         if (WhiteOnMove(forwardMostMove)) {
14369             DisplayError(_("Wait until your turn"), 0);
14370             return;
14371         }
14372         break;
14373       case BeginningOfGame:
14374       case MachinePlaysBlack:
14375         if (!WhiteOnMove(forwardMostMove)) {
14376             DisplayError(_("Wait until your turn"), 0);
14377             return;
14378         }
14379         break;
14380       case EditPosition:
14381         EditPositionDone(TRUE);
14382         break;
14383       case TwoMachinesPlay:
14384         return;
14385       default:
14386         break;
14387     }
14388     SendToProgram("bk\n", &first);
14389     bookOutput[0] = NULLCHAR;
14390     bookRequested = TRUE;
14391 }
14392
14393 void
14394 AboutGameEvent()
14395 {
14396     char *tags = PGNTags(&gameInfo);
14397     TagsPopUp(tags, CmailMsg());
14398     free(tags);
14399 }
14400
14401 /* end button procedures */
14402
14403 void
14404 PrintPosition(fp, move)
14405      FILE *fp;
14406      int move;
14407 {
14408     int i, j;
14409
14410     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14411         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14412             char c = PieceToChar(boards[move][i][j]);
14413             fputc(c == 'x' ? '.' : c, fp);
14414             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14415         }
14416     }
14417     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14418       fprintf(fp, "white to play\n");
14419     else
14420       fprintf(fp, "black to play\n");
14421 }
14422
14423 void
14424 PrintOpponents(fp)
14425      FILE *fp;
14426 {
14427     if (gameInfo.white != NULL) {
14428         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14429     } else {
14430         fprintf(fp, "\n");
14431     }
14432 }
14433
14434 /* Find last component of program's own name, using some heuristics */
14435 void
14436 TidyProgramName(prog, host, buf)
14437      char *prog, *host, buf[MSG_SIZ];
14438 {
14439     char *p, *q;
14440     int local = (strcmp(host, "localhost") == 0);
14441     while (!local && (p = strchr(prog, ';')) != NULL) {
14442         p++;
14443         while (*p == ' ') p++;
14444         prog = p;
14445     }
14446     if (*prog == '"' || *prog == '\'') {
14447         q = strchr(prog + 1, *prog);
14448     } else {
14449         q = strchr(prog, ' ');
14450     }
14451     if (q == NULL) q = prog + strlen(prog);
14452     p = q;
14453     while (p >= prog && *p != '/' && *p != '\\') p--;
14454     p++;
14455     if(p == prog && *p == '"') p++;
14456     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14457     memcpy(buf, p, q - p);
14458     buf[q - p] = NULLCHAR;
14459     if (!local) {
14460         strcat(buf, "@");
14461         strcat(buf, host);
14462     }
14463 }
14464
14465 char *
14466 TimeControlTagValue()
14467 {
14468     char buf[MSG_SIZ];
14469     if (!appData.clockMode) {
14470       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14471     } else if (movesPerSession > 0) {
14472       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14473     } else if (timeIncrement == 0) {
14474       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14475     } else {
14476       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14477     }
14478     return StrSave(buf);
14479 }
14480
14481 void
14482 SetGameInfo()
14483 {
14484     /* This routine is used only for certain modes */
14485     VariantClass v = gameInfo.variant;
14486     ChessMove r = GameUnfinished;
14487     char *p = NULL;
14488
14489     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14490         r = gameInfo.result;
14491         p = gameInfo.resultDetails;
14492         gameInfo.resultDetails = NULL;
14493     }
14494     ClearGameInfo(&gameInfo);
14495     gameInfo.variant = v;
14496
14497     switch (gameMode) {
14498       case MachinePlaysWhite:
14499         gameInfo.event = StrSave( appData.pgnEventHeader );
14500         gameInfo.site = StrSave(HostName());
14501         gameInfo.date = PGNDate();
14502         gameInfo.round = StrSave("-");
14503         gameInfo.white = StrSave(first.tidy);
14504         gameInfo.black = StrSave(UserName());
14505         gameInfo.timeControl = TimeControlTagValue();
14506         break;
14507
14508       case MachinePlaysBlack:
14509         gameInfo.event = StrSave( appData.pgnEventHeader );
14510         gameInfo.site = StrSave(HostName());
14511         gameInfo.date = PGNDate();
14512         gameInfo.round = StrSave("-");
14513         gameInfo.white = StrSave(UserName());
14514         gameInfo.black = StrSave(first.tidy);
14515         gameInfo.timeControl = TimeControlTagValue();
14516         break;
14517
14518       case TwoMachinesPlay:
14519         gameInfo.event = StrSave( appData.pgnEventHeader );
14520         gameInfo.site = StrSave(HostName());
14521         gameInfo.date = PGNDate();
14522         if (roundNr > 0) {
14523             char buf[MSG_SIZ];
14524             snprintf(buf, MSG_SIZ, "%d", roundNr);
14525             gameInfo.round = StrSave(buf);
14526         } else {
14527             gameInfo.round = StrSave("-");
14528         }
14529         if (first.twoMachinesColor[0] == 'w') {
14530             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14531             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14532         } else {
14533             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14534             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14535         }
14536         gameInfo.timeControl = TimeControlTagValue();
14537         break;
14538
14539       case EditGame:
14540         gameInfo.event = StrSave("Edited game");
14541         gameInfo.site = StrSave(HostName());
14542         gameInfo.date = PGNDate();
14543         gameInfo.round = StrSave("-");
14544         gameInfo.white = StrSave("-");
14545         gameInfo.black = StrSave("-");
14546         gameInfo.result = r;
14547         gameInfo.resultDetails = p;
14548         break;
14549
14550       case EditPosition:
14551         gameInfo.event = StrSave("Edited position");
14552         gameInfo.site = StrSave(HostName());
14553         gameInfo.date = PGNDate();
14554         gameInfo.round = StrSave("-");
14555         gameInfo.white = StrSave("-");
14556         gameInfo.black = StrSave("-");
14557         break;
14558
14559       case IcsPlayingWhite:
14560       case IcsPlayingBlack:
14561       case IcsObserving:
14562       case IcsExamining:
14563         break;
14564
14565       case PlayFromGameFile:
14566         gameInfo.event = StrSave("Game from non-PGN file");
14567         gameInfo.site = StrSave(HostName());
14568         gameInfo.date = PGNDate();
14569         gameInfo.round = StrSave("-");
14570         gameInfo.white = StrSave("?");
14571         gameInfo.black = StrSave("?");
14572         break;
14573
14574       default:
14575         break;
14576     }
14577 }
14578
14579 void
14580 ReplaceComment(index, text)
14581      int index;
14582      char *text;
14583 {
14584     int len;
14585     char *p;
14586     float score;
14587
14588     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14589        pvInfoList[index-1].depth == len &&
14590        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14591        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14592     while (*text == '\n') text++;
14593     len = strlen(text);
14594     while (len > 0 && text[len - 1] == '\n') len--;
14595
14596     if (commentList[index] != NULL)
14597       free(commentList[index]);
14598
14599     if (len == 0) {
14600         commentList[index] = NULL;
14601         return;
14602     }
14603   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14604       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14605       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14606     commentList[index] = (char *) malloc(len + 2);
14607     strncpy(commentList[index], text, len);
14608     commentList[index][len] = '\n';
14609     commentList[index][len + 1] = NULLCHAR;
14610   } else {
14611     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14612     char *p;
14613     commentList[index] = (char *) malloc(len + 7);
14614     safeStrCpy(commentList[index], "{\n", 3);
14615     safeStrCpy(commentList[index]+2, text, len+1);
14616     commentList[index][len+2] = NULLCHAR;
14617     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14618     strcat(commentList[index], "\n}\n");
14619   }
14620 }
14621
14622 void
14623 CrushCRs(text)
14624      char *text;
14625 {
14626   char *p = text;
14627   char *q = text;
14628   char ch;
14629
14630   do {
14631     ch = *p++;
14632     if (ch == '\r') continue;
14633     *q++ = ch;
14634   } while (ch != '\0');
14635 }
14636
14637 void
14638 AppendComment(index, text, addBraces)
14639      int index;
14640      char *text;
14641      Boolean addBraces; // [HGM] braces: tells if we should add {}
14642 {
14643     int oldlen, len;
14644     char *old;
14645
14646 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14647     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14648
14649     CrushCRs(text);
14650     while (*text == '\n') text++;
14651     len = strlen(text);
14652     while (len > 0 && text[len - 1] == '\n') len--;
14653
14654     if (len == 0) return;
14655
14656     if (commentList[index] != NULL) {
14657       Boolean addClosingBrace = addBraces;
14658         old = commentList[index];
14659         oldlen = strlen(old);
14660         while(commentList[index][oldlen-1] ==  '\n')
14661           commentList[index][--oldlen] = NULLCHAR;
14662         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14663         safeStrCpy(commentList[index], old, oldlen + len + 6);
14664         free(old);
14665         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14666         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14667           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14668           while (*text == '\n') { text++; len--; }
14669           commentList[index][--oldlen] = NULLCHAR;
14670       }
14671         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14672         else          strcat(commentList[index], "\n");
14673         strcat(commentList[index], text);
14674         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14675         else          strcat(commentList[index], "\n");
14676     } else {
14677         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14678         if(addBraces)
14679           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14680         else commentList[index][0] = NULLCHAR;
14681         strcat(commentList[index], text);
14682         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14683         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14684     }
14685 }
14686
14687 static char * FindStr( char * text, char * sub_text )
14688 {
14689     char * result = strstr( text, sub_text );
14690
14691     if( result != NULL ) {
14692         result += strlen( sub_text );
14693     }
14694
14695     return result;
14696 }
14697
14698 /* [AS] Try to extract PV info from PGN comment */
14699 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14700 char *GetInfoFromComment( int index, char * text )
14701 {
14702     char * sep = text, *p;
14703
14704     if( text != NULL && index > 0 ) {
14705         int score = 0;
14706         int depth = 0;
14707         int time = -1, sec = 0, deci;
14708         char * s_eval = FindStr( text, "[%eval " );
14709         char * s_emt = FindStr( text, "[%emt " );
14710
14711         if( s_eval != NULL || s_emt != NULL ) {
14712             /* New style */
14713             char delim;
14714
14715             if( s_eval != NULL ) {
14716                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14717                     return text;
14718                 }
14719
14720                 if( delim != ']' ) {
14721                     return text;
14722                 }
14723             }
14724
14725             if( s_emt != NULL ) {
14726             }
14727                 return text;
14728         }
14729         else {
14730             /* We expect something like: [+|-]nnn.nn/dd */
14731             int score_lo = 0;
14732
14733             if(*text != '{') return text; // [HGM] braces: must be normal comment
14734
14735             sep = strchr( text, '/' );
14736             if( sep == NULL || sep < (text+4) ) {
14737                 return text;
14738             }
14739
14740             p = text;
14741             if(p[1] == '(') { // comment starts with PV
14742                p = strchr(p, ')'); // locate end of PV
14743                if(p == NULL || sep < p+5) return text;
14744                // at this point we have something like "{(.*) +0.23/6 ..."
14745                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14746                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14747                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14748             }
14749             time = -1; sec = -1; deci = -1;
14750             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14751                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14752                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14753                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14754                 return text;
14755             }
14756
14757             if( score_lo < 0 || score_lo >= 100 ) {
14758                 return text;
14759             }
14760
14761             if(sec >= 0) time = 600*time + 10*sec; else
14762             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14763
14764             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14765
14766             /* [HGM] PV time: now locate end of PV info */
14767             while( *++sep >= '0' && *sep <= '9'); // strip depth
14768             if(time >= 0)
14769             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14770             if(sec >= 0)
14771             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14772             if(deci >= 0)
14773             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14774             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14775         }
14776
14777         if( depth <= 0 ) {
14778             return text;
14779         }
14780
14781         if( time < 0 ) {
14782             time = -1;
14783         }
14784
14785         pvInfoList[index-1].depth = depth;
14786         pvInfoList[index-1].score = score;
14787         pvInfoList[index-1].time  = 10*time; // centi-sec
14788         if(*sep == '}') *sep = 0; else *--sep = '{';
14789         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14790     }
14791     return sep;
14792 }
14793
14794 void
14795 SendToProgram(message, cps)
14796      char *message;
14797      ChessProgramState *cps;
14798 {
14799     int count, outCount, error;
14800     char buf[MSG_SIZ];
14801
14802     if (cps->pr == NULL) return;
14803     Attention(cps);
14804
14805     if (appData.debugMode) {
14806         TimeMark now;
14807         GetTimeMark(&now);
14808         fprintf(debugFP, "%ld >%-6s: %s",
14809                 SubtractTimeMarks(&now, &programStartTime),
14810                 cps->which, message);
14811     }
14812
14813     count = strlen(message);
14814     outCount = OutputToProcess(cps->pr, message, count, &error);
14815     if (outCount < count && !exiting
14816                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14817       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14818       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14819         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14820             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14821                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14822                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14823                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14824             } else {
14825                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14826                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14827                 gameInfo.result = res;
14828             }
14829             gameInfo.resultDetails = StrSave(buf);
14830         }
14831         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14832         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14833     }
14834 }
14835
14836 void
14837 ReceiveFromProgram(isr, closure, message, count, error)
14838      InputSourceRef isr;
14839      VOIDSTAR closure;
14840      char *message;
14841      int count;
14842      int error;
14843 {
14844     char *end_str;
14845     char buf[MSG_SIZ];
14846     ChessProgramState *cps = (ChessProgramState *)closure;
14847
14848     if (isr != cps->isr) return; /* Killed intentionally */
14849     if (count <= 0) {
14850         if (count == 0) {
14851             RemoveInputSource(cps->isr);
14852             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14853             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14854                     _(cps->which), cps->program);
14855         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14856                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14857                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14858                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14859                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14860                 } else {
14861                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14862                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14863                     gameInfo.result = res;
14864                 }
14865                 gameInfo.resultDetails = StrSave(buf);
14866             }
14867             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14868             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14869         } else {
14870             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14871                     _(cps->which), cps->program);
14872             RemoveInputSource(cps->isr);
14873
14874             /* [AS] Program is misbehaving badly... kill it */
14875             if( count == -2 ) {
14876                 DestroyChildProcess( cps->pr, 9 );
14877                 cps->pr = NoProc;
14878             }
14879
14880             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14881         }
14882         return;
14883     }
14884
14885     if ((end_str = strchr(message, '\r')) != NULL)
14886       *end_str = NULLCHAR;
14887     if ((end_str = strchr(message, '\n')) != NULL)
14888       *end_str = NULLCHAR;
14889
14890     if (appData.debugMode) {
14891         TimeMark now; int print = 1;
14892         char *quote = ""; char c; int i;
14893
14894         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14895                 char start = message[0];
14896                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14897                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14898                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14899                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14900                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14901                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14902                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14903                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14904                    sscanf(message, "hint: %c", &c)!=1 && 
14905                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14906                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14907                     print = (appData.engineComments >= 2);
14908                 }
14909                 message[0] = start; // restore original message
14910         }
14911         if(print) {
14912                 GetTimeMark(&now);
14913                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14914                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14915                         quote,
14916                         message);
14917         }
14918     }
14919
14920     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14921     if (appData.icsEngineAnalyze) {
14922         if (strstr(message, "whisper") != NULL ||
14923              strstr(message, "kibitz") != NULL ||
14924             strstr(message, "tellics") != NULL) return;
14925     }
14926
14927     HandleMachineMove(message, cps);
14928 }
14929
14930
14931 void
14932 SendTimeControl(cps, mps, tc, inc, sd, st)
14933      ChessProgramState *cps;
14934      int mps, inc, sd, st;
14935      long tc;
14936 {
14937     char buf[MSG_SIZ];
14938     int seconds;
14939
14940     if( timeControl_2 > 0 ) {
14941         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14942             tc = timeControl_2;
14943         }
14944     }
14945     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14946     inc /= cps->timeOdds;
14947     st  /= cps->timeOdds;
14948
14949     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14950
14951     if (st > 0) {
14952       /* Set exact time per move, normally using st command */
14953       if (cps->stKludge) {
14954         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14955         seconds = st % 60;
14956         if (seconds == 0) {
14957           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14958         } else {
14959           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14960         }
14961       } else {
14962         snprintf(buf, MSG_SIZ, "st %d\n", st);
14963       }
14964     } else {
14965       /* Set conventional or incremental time control, using level command */
14966       if (seconds == 0) {
14967         /* Note old gnuchess bug -- minutes:seconds used to not work.
14968            Fixed in later versions, but still avoid :seconds
14969            when seconds is 0. */
14970         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14971       } else {
14972         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14973                  seconds, inc/1000.);
14974       }
14975     }
14976     SendToProgram(buf, cps);
14977
14978     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14979     /* Orthogonally, limit search to given depth */
14980     if (sd > 0) {
14981       if (cps->sdKludge) {
14982         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14983       } else {
14984         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14985       }
14986       SendToProgram(buf, cps);
14987     }
14988
14989     if(cps->nps >= 0) { /* [HGM] nps */
14990         if(cps->supportsNPS == FALSE)
14991           cps->nps = -1; // don't use if engine explicitly says not supported!
14992         else {
14993           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14994           SendToProgram(buf, cps);
14995         }
14996     }
14997 }
14998
14999 ChessProgramState *WhitePlayer()
15000 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15001 {
15002     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15003        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15004         return &second;
15005     return &first;
15006 }
15007
15008 void
15009 SendTimeRemaining(cps, machineWhite)
15010      ChessProgramState *cps;
15011      int /*boolean*/ machineWhite;
15012 {
15013     char message[MSG_SIZ];
15014     long time, otime;
15015
15016     /* Note: this routine must be called when the clocks are stopped
15017        or when they have *just* been set or switched; otherwise
15018        it will be off by the time since the current tick started.
15019     */
15020     if (machineWhite) {
15021         time = whiteTimeRemaining / 10;
15022         otime = blackTimeRemaining / 10;
15023     } else {
15024         time = blackTimeRemaining / 10;
15025         otime = whiteTimeRemaining / 10;
15026     }
15027     /* [HGM] translate opponent's time by time-odds factor */
15028     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15029     if (appData.debugMode) {
15030         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15031     }
15032
15033     if (time <= 0) time = 1;
15034     if (otime <= 0) otime = 1;
15035
15036     snprintf(message, MSG_SIZ, "time %ld\n", time);
15037     SendToProgram(message, cps);
15038
15039     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15040     SendToProgram(message, cps);
15041 }
15042
15043 int
15044 BoolFeature(p, name, loc, cps)
15045      char **p;
15046      char *name;
15047      int *loc;
15048      ChessProgramState *cps;
15049 {
15050   char buf[MSG_SIZ];
15051   int len = strlen(name);
15052   int val;
15053
15054   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15055     (*p) += len + 1;
15056     sscanf(*p, "%d", &val);
15057     *loc = (val != 0);
15058     while (**p && **p != ' ')
15059       (*p)++;
15060     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15061     SendToProgram(buf, cps);
15062     return TRUE;
15063   }
15064   return FALSE;
15065 }
15066
15067 int
15068 IntFeature(p, name, loc, cps)
15069      char **p;
15070      char *name;
15071      int *loc;
15072      ChessProgramState *cps;
15073 {
15074   char buf[MSG_SIZ];
15075   int len = strlen(name);
15076   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15077     (*p) += len + 1;
15078     sscanf(*p, "%d", loc);
15079     while (**p && **p != ' ') (*p)++;
15080     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15081     SendToProgram(buf, cps);
15082     return TRUE;
15083   }
15084   return FALSE;
15085 }
15086
15087 int
15088 StringFeature(p, name, loc, cps)
15089      char **p;
15090      char *name;
15091      char loc[];
15092      ChessProgramState *cps;
15093 {
15094   char buf[MSG_SIZ];
15095   int len = strlen(name);
15096   if (strncmp((*p), name, len) == 0
15097       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15098     (*p) += len + 2;
15099     sscanf(*p, "%[^\"]", loc);
15100     while (**p && **p != '\"') (*p)++;
15101     if (**p == '\"') (*p)++;
15102     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15103     SendToProgram(buf, cps);
15104     return TRUE;
15105   }
15106   return FALSE;
15107 }
15108
15109 int
15110 ParseOption(Option *opt, ChessProgramState *cps)
15111 // [HGM] options: process the string that defines an engine option, and determine
15112 // name, type, default value, and allowed value range
15113 {
15114         char *p, *q, buf[MSG_SIZ];
15115         int n, min = (-1)<<31, max = 1<<31, def;
15116
15117         if(p = strstr(opt->name, " -spin ")) {
15118             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15119             if(max < min) max = min; // enforce consistency
15120             if(def < min) def = min;
15121             if(def > max) def = max;
15122             opt->value = def;
15123             opt->min = min;
15124             opt->max = max;
15125             opt->type = Spin;
15126         } else if((p = strstr(opt->name, " -slider "))) {
15127             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15128             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15129             if(max < min) max = min; // enforce consistency
15130             if(def < min) def = min;
15131             if(def > max) def = max;
15132             opt->value = def;
15133             opt->min = min;
15134             opt->max = max;
15135             opt->type = Spin; // Slider;
15136         } else if((p = strstr(opt->name, " -string "))) {
15137             opt->textValue = p+9;
15138             opt->type = TextBox;
15139         } else if((p = strstr(opt->name, " -file "))) {
15140             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15141             opt->textValue = p+7;
15142             opt->type = FileName; // FileName;
15143         } else if((p = strstr(opt->name, " -path "))) {
15144             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15145             opt->textValue = p+7;
15146             opt->type = PathName; // PathName;
15147         } else if(p = strstr(opt->name, " -check ")) {
15148             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15149             opt->value = (def != 0);
15150             opt->type = CheckBox;
15151         } else if(p = strstr(opt->name, " -combo ")) {
15152             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15153             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15154             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15155             opt->value = n = 0;
15156             while(q = StrStr(q, " /// ")) {
15157                 n++; *q = 0;    // count choices, and null-terminate each of them
15158                 q += 5;
15159                 if(*q == '*') { // remember default, which is marked with * prefix
15160                     q++;
15161                     opt->value = n;
15162                 }
15163                 cps->comboList[cps->comboCnt++] = q;
15164             }
15165             cps->comboList[cps->comboCnt++] = NULL;
15166             opt->max = n + 1;
15167             opt->type = ComboBox;
15168         } else if(p = strstr(opt->name, " -button")) {
15169             opt->type = Button;
15170         } else if(p = strstr(opt->name, " -save")) {
15171             opt->type = SaveButton;
15172         } else return FALSE;
15173         *p = 0; // terminate option name
15174         // now look if the command-line options define a setting for this engine option.
15175         if(cps->optionSettings && cps->optionSettings[0])
15176             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15177         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15178           snprintf(buf, MSG_SIZ, "option %s", p);
15179                 if(p = strstr(buf, ",")) *p = 0;
15180                 if(q = strchr(buf, '=')) switch(opt->type) {
15181                     case ComboBox:
15182                         for(n=0; n<opt->max; n++)
15183                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15184                         break;
15185                     case TextBox:
15186                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15187                         break;
15188                     case Spin:
15189                     case CheckBox:
15190                         opt->value = atoi(q+1);
15191                     default:
15192                         break;
15193                 }
15194                 strcat(buf, "\n");
15195                 SendToProgram(buf, cps);
15196         }
15197         return TRUE;
15198 }
15199
15200 void
15201 FeatureDone(cps, val)
15202      ChessProgramState* cps;
15203      int val;
15204 {
15205   DelayedEventCallback cb = GetDelayedEvent();
15206   if ((cb == InitBackEnd3 && cps == &first) ||
15207       (cb == SettingsMenuIfReady && cps == &second) ||
15208       (cb == LoadEngine) ||
15209       (cb == TwoMachinesEventIfReady)) {
15210     CancelDelayedEvent();
15211     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15212   }
15213   cps->initDone = val;
15214 }
15215
15216 /* Parse feature command from engine */
15217 void
15218 ParseFeatures(args, cps)
15219      char* args;
15220      ChessProgramState *cps;
15221 {
15222   char *p = args;
15223   char *q;
15224   int val;
15225   char buf[MSG_SIZ];
15226
15227   for (;;) {
15228     while (*p == ' ') p++;
15229     if (*p == NULLCHAR) return;
15230
15231     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15232     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15233     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15234     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15235     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15236     if (BoolFeature(&p, "reuse", &val, cps)) {
15237       /* Engine can disable reuse, but can't enable it if user said no */
15238       if (!val) cps->reuse = FALSE;
15239       continue;
15240     }
15241     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15242     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15243       if (gameMode == TwoMachinesPlay) {
15244         DisplayTwoMachinesTitle();
15245       } else {
15246         DisplayTitle("");
15247       }
15248       continue;
15249     }
15250     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15251     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15252     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15253     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15254     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15255     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15256     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15257     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15258     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15259     if (IntFeature(&p, "done", &val, cps)) {
15260       FeatureDone(cps, val);
15261       continue;
15262     }
15263     /* Added by Tord: */
15264     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15265     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15266     /* End of additions by Tord */
15267
15268     /* [HGM] added features: */
15269     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15270     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15271     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15272     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15273     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15274     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15275     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15276         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15277           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15278             SendToProgram(buf, cps);
15279             continue;
15280         }
15281         if(cps->nrOptions >= MAX_OPTIONS) {
15282             cps->nrOptions--;
15283             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15284             DisplayError(buf, 0);
15285         }
15286         continue;
15287     }
15288     /* End of additions by HGM */
15289
15290     /* unknown feature: complain and skip */
15291     q = p;
15292     while (*q && *q != '=') q++;
15293     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15294     SendToProgram(buf, cps);
15295     p = q;
15296     if (*p == '=') {
15297       p++;
15298       if (*p == '\"') {
15299         p++;
15300         while (*p && *p != '\"') p++;
15301         if (*p == '\"') p++;
15302       } else {
15303         while (*p && *p != ' ') p++;
15304       }
15305     }
15306   }
15307
15308 }
15309
15310 void
15311 PeriodicUpdatesEvent(newState)
15312      int newState;
15313 {
15314     if (newState == appData.periodicUpdates)
15315       return;
15316
15317     appData.periodicUpdates=newState;
15318
15319     /* Display type changes, so update it now */
15320 //    DisplayAnalysis();
15321
15322     /* Get the ball rolling again... */
15323     if (newState) {
15324         AnalysisPeriodicEvent(1);
15325         StartAnalysisClock();
15326     }
15327 }
15328
15329 void
15330 PonderNextMoveEvent(newState)
15331      int newState;
15332 {
15333     if (newState == appData.ponderNextMove) return;
15334     if (gameMode == EditPosition) EditPositionDone(TRUE);
15335     if (newState) {
15336         SendToProgram("hard\n", &first);
15337         if (gameMode == TwoMachinesPlay) {
15338             SendToProgram("hard\n", &second);
15339         }
15340     } else {
15341         SendToProgram("easy\n", &first);
15342         thinkOutput[0] = NULLCHAR;
15343         if (gameMode == TwoMachinesPlay) {
15344             SendToProgram("easy\n", &second);
15345         }
15346     }
15347     appData.ponderNextMove = newState;
15348 }
15349
15350 void
15351 NewSettingEvent(option, feature, command, value)
15352      char *command;
15353      int option, value, *feature;
15354 {
15355     char buf[MSG_SIZ];
15356
15357     if (gameMode == EditPosition) EditPositionDone(TRUE);
15358     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15359     if(feature == NULL || *feature) SendToProgram(buf, &first);
15360     if (gameMode == TwoMachinesPlay) {
15361         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15362     }
15363 }
15364
15365 void
15366 ShowThinkingEvent()
15367 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15368 {
15369     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15370     int newState = appData.showThinking
15371         // [HGM] thinking: other features now need thinking output as well
15372         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15373
15374     if (oldState == newState) return;
15375     oldState = newState;
15376     if (gameMode == EditPosition) EditPositionDone(TRUE);
15377     if (oldState) {
15378         SendToProgram("post\n", &first);
15379         if (gameMode == TwoMachinesPlay) {
15380             SendToProgram("post\n", &second);
15381         }
15382     } else {
15383         SendToProgram("nopost\n", &first);
15384         thinkOutput[0] = NULLCHAR;
15385         if (gameMode == TwoMachinesPlay) {
15386             SendToProgram("nopost\n", &second);
15387         }
15388     }
15389 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15390 }
15391
15392 void
15393 AskQuestionEvent(title, question, replyPrefix, which)
15394      char *title; char *question; char *replyPrefix; char *which;
15395 {
15396   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15397   if (pr == NoProc) return;
15398   AskQuestion(title, question, replyPrefix, pr);
15399 }
15400
15401 void
15402 TypeInEvent(char firstChar)
15403 {
15404     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15405         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15406         gameMode == AnalyzeMode || gameMode == EditGame || 
15407         gameMode == EditPosition || gameMode == IcsExamining ||
15408         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15409         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15410                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15411                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15412         gameMode == Training) PopUpMoveDialog(firstChar);
15413 }
15414
15415 void
15416 TypeInDoneEvent(char *move)
15417 {
15418         Board board;
15419         int n, fromX, fromY, toX, toY;
15420         char promoChar;
15421         ChessMove moveType;
15422
15423         // [HGM] FENedit
15424         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15425                 EditPositionPasteFEN(move);
15426                 return;
15427         }
15428         // [HGM] movenum: allow move number to be typed in any mode
15429         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15430           ToNrEvent(2*n-1);
15431           return;
15432         }
15433
15434       if (gameMode != EditGame && currentMove != forwardMostMove && 
15435         gameMode != Training) {
15436         DisplayMoveError(_("Displayed move is not current"));
15437       } else {
15438         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15439           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15440         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15441         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15442           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15443           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15444         } else {
15445           DisplayMoveError(_("Could not parse move"));
15446         }
15447       }
15448 }
15449
15450 void
15451 DisplayMove(moveNumber)
15452      int moveNumber;
15453 {
15454     char message[MSG_SIZ];
15455     char res[MSG_SIZ];
15456     char cpThinkOutput[MSG_SIZ];
15457
15458     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15459
15460     if (moveNumber == forwardMostMove - 1 ||
15461         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15462
15463         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15464
15465         if (strchr(cpThinkOutput, '\n')) {
15466             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15467         }
15468     } else {
15469         *cpThinkOutput = NULLCHAR;
15470     }
15471
15472     /* [AS] Hide thinking from human user */
15473     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15474         *cpThinkOutput = NULLCHAR;
15475         if( thinkOutput[0] != NULLCHAR ) {
15476             int i;
15477
15478             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15479                 cpThinkOutput[i] = '.';
15480             }
15481             cpThinkOutput[i] = NULLCHAR;
15482             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15483         }
15484     }
15485
15486     if (moveNumber == forwardMostMove - 1 &&
15487         gameInfo.resultDetails != NULL) {
15488         if (gameInfo.resultDetails[0] == NULLCHAR) {
15489           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15490         } else {
15491           snprintf(res, MSG_SIZ, " {%s} %s",
15492                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15493         }
15494     } else {
15495         res[0] = NULLCHAR;
15496     }
15497
15498     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15499         DisplayMessage(res, cpThinkOutput);
15500     } else {
15501       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15502                 WhiteOnMove(moveNumber) ? " " : ".. ",
15503                 parseList[moveNumber], res);
15504         DisplayMessage(message, cpThinkOutput);
15505     }
15506 }
15507
15508 void
15509 DisplayComment(moveNumber, text)
15510      int moveNumber;
15511      char *text;
15512 {
15513     char title[MSG_SIZ];
15514
15515     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15516       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15517     } else {
15518       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15519               WhiteOnMove(moveNumber) ? " " : ".. ",
15520               parseList[moveNumber]);
15521     }
15522     if (text != NULL && (appData.autoDisplayComment || commentUp))
15523         CommentPopUp(title, text);
15524 }
15525
15526 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15527  * might be busy thinking or pondering.  It can be omitted if your
15528  * gnuchess is configured to stop thinking immediately on any user
15529  * input.  However, that gnuchess feature depends on the FIONREAD
15530  * ioctl, which does not work properly on some flavors of Unix.
15531  */
15532 void
15533 Attention(cps)
15534      ChessProgramState *cps;
15535 {
15536 #if ATTENTION
15537     if (!cps->useSigint) return;
15538     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15539     switch (gameMode) {
15540       case MachinePlaysWhite:
15541       case MachinePlaysBlack:
15542       case TwoMachinesPlay:
15543       case IcsPlayingWhite:
15544       case IcsPlayingBlack:
15545       case AnalyzeMode:
15546       case AnalyzeFile:
15547         /* Skip if we know it isn't thinking */
15548         if (!cps->maybeThinking) return;
15549         if (appData.debugMode)
15550           fprintf(debugFP, "Interrupting %s\n", cps->which);
15551         InterruptChildProcess(cps->pr);
15552         cps->maybeThinking = FALSE;
15553         break;
15554       default:
15555         break;
15556     }
15557 #endif /*ATTENTION*/
15558 }
15559
15560 int
15561 CheckFlags()
15562 {
15563     if (whiteTimeRemaining <= 0) {
15564         if (!whiteFlag) {
15565             whiteFlag = TRUE;
15566             if (appData.icsActive) {
15567                 if (appData.autoCallFlag &&
15568                     gameMode == IcsPlayingBlack && !blackFlag) {
15569                   SendToICS(ics_prefix);
15570                   SendToICS("flag\n");
15571                 }
15572             } else {
15573                 if (blackFlag) {
15574                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15575                 } else {
15576                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15577                     if (appData.autoCallFlag) {
15578                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15579                         return TRUE;
15580                     }
15581                 }
15582             }
15583         }
15584     }
15585     if (blackTimeRemaining <= 0) {
15586         if (!blackFlag) {
15587             blackFlag = TRUE;
15588             if (appData.icsActive) {
15589                 if (appData.autoCallFlag &&
15590                     gameMode == IcsPlayingWhite && !whiteFlag) {
15591                   SendToICS(ics_prefix);
15592                   SendToICS("flag\n");
15593                 }
15594             } else {
15595                 if (whiteFlag) {
15596                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15597                 } else {
15598                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15599                     if (appData.autoCallFlag) {
15600                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15601                         return TRUE;
15602                     }
15603                 }
15604             }
15605         }
15606     }
15607     return FALSE;
15608 }
15609
15610 void
15611 CheckTimeControl()
15612 {
15613     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15614         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15615
15616     /*
15617      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15618      */
15619     if ( !WhiteOnMove(forwardMostMove) ) {
15620         /* White made time control */
15621         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15622         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15623         /* [HGM] time odds: correct new time quota for time odds! */
15624                                             / WhitePlayer()->timeOdds;
15625         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15626     } else {
15627         lastBlack -= blackTimeRemaining;
15628         /* Black made time control */
15629         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15630                                             / WhitePlayer()->other->timeOdds;
15631         lastWhite = whiteTimeRemaining;
15632     }
15633 }
15634
15635 void
15636 DisplayBothClocks()
15637 {
15638     int wom = gameMode == EditPosition ?
15639       !blackPlaysFirst : WhiteOnMove(currentMove);
15640     DisplayWhiteClock(whiteTimeRemaining, wom);
15641     DisplayBlackClock(blackTimeRemaining, !wom);
15642 }
15643
15644
15645 /* Timekeeping seems to be a portability nightmare.  I think everyone
15646    has ftime(), but I'm really not sure, so I'm including some ifdefs
15647    to use other calls if you don't.  Clocks will be less accurate if
15648    you have neither ftime nor gettimeofday.
15649 */
15650
15651 /* VS 2008 requires the #include outside of the function */
15652 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15653 #include <sys/timeb.h>
15654 #endif
15655
15656 /* Get the current time as a TimeMark */
15657 void
15658 GetTimeMark(tm)
15659      TimeMark *tm;
15660 {
15661 #if HAVE_GETTIMEOFDAY
15662
15663     struct timeval timeVal;
15664     struct timezone timeZone;
15665
15666     gettimeofday(&timeVal, &timeZone);
15667     tm->sec = (long) timeVal.tv_sec;
15668     tm->ms = (int) (timeVal.tv_usec / 1000L);
15669
15670 #else /*!HAVE_GETTIMEOFDAY*/
15671 #if HAVE_FTIME
15672
15673 // include <sys/timeb.h> / moved to just above start of function
15674     struct timeb timeB;
15675
15676     ftime(&timeB);
15677     tm->sec = (long) timeB.time;
15678     tm->ms = (int) timeB.millitm;
15679
15680 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15681     tm->sec = (long) time(NULL);
15682     tm->ms = 0;
15683 #endif
15684 #endif
15685 }
15686
15687 /* Return the difference in milliseconds between two
15688    time marks.  We assume the difference will fit in a long!
15689 */
15690 long
15691 SubtractTimeMarks(tm2, tm1)
15692      TimeMark *tm2, *tm1;
15693 {
15694     return 1000L*(tm2->sec - tm1->sec) +
15695            (long) (tm2->ms - tm1->ms);
15696 }
15697
15698
15699 /*
15700  * Code to manage the game clocks.
15701  *
15702  * In tournament play, black starts the clock and then white makes a move.
15703  * We give the human user a slight advantage if he is playing white---the
15704  * clocks don't run until he makes his first move, so it takes zero time.
15705  * Also, we don't account for network lag, so we could get out of sync
15706  * with GNU Chess's clock -- but then, referees are always right.
15707  */
15708
15709 static TimeMark tickStartTM;
15710 static long intendedTickLength;
15711
15712 long
15713 NextTickLength(timeRemaining)
15714      long timeRemaining;
15715 {
15716     long nominalTickLength, nextTickLength;
15717
15718     if (timeRemaining > 0L && timeRemaining <= 10000L)
15719       nominalTickLength = 100L;
15720     else
15721       nominalTickLength = 1000L;
15722     nextTickLength = timeRemaining % nominalTickLength;
15723     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15724
15725     return nextTickLength;
15726 }
15727
15728 /* Adjust clock one minute up or down */
15729 void
15730 AdjustClock(Boolean which, int dir)
15731 {
15732     if(which) blackTimeRemaining += 60000*dir;
15733     else      whiteTimeRemaining += 60000*dir;
15734     DisplayBothClocks();
15735 }
15736
15737 /* Stop clocks and reset to a fresh time control */
15738 void
15739 ResetClocks()
15740 {
15741     (void) StopClockTimer();
15742     if (appData.icsActive) {
15743         whiteTimeRemaining = blackTimeRemaining = 0;
15744     } else if (searchTime) {
15745         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15746         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15747     } else { /* [HGM] correct new time quote for time odds */
15748         whiteTC = blackTC = fullTimeControlString;
15749         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15750         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15751     }
15752     if (whiteFlag || blackFlag) {
15753         DisplayTitle("");
15754         whiteFlag = blackFlag = FALSE;
15755     }
15756     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15757     DisplayBothClocks();
15758 }
15759
15760 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15761
15762 /* Decrement running clock by amount of time that has passed */
15763 void
15764 DecrementClocks()
15765 {
15766     long timeRemaining;
15767     long lastTickLength, fudge;
15768     TimeMark now;
15769
15770     if (!appData.clockMode) return;
15771     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15772
15773     GetTimeMark(&now);
15774
15775     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15776
15777     /* Fudge if we woke up a little too soon */
15778     fudge = intendedTickLength - lastTickLength;
15779     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15780
15781     if (WhiteOnMove(forwardMostMove)) {
15782         if(whiteNPS >= 0) lastTickLength = 0;
15783         timeRemaining = whiteTimeRemaining -= lastTickLength;
15784         if(timeRemaining < 0 && !appData.icsActive) {
15785             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15786             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15787                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15788                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15789             }
15790         }
15791         DisplayWhiteClock(whiteTimeRemaining - fudge,
15792                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15793     } else {
15794         if(blackNPS >= 0) lastTickLength = 0;
15795         timeRemaining = blackTimeRemaining -= lastTickLength;
15796         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15797             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15798             if(suddenDeath) {
15799                 blackStartMove = forwardMostMove;
15800                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15801             }
15802         }
15803         DisplayBlackClock(blackTimeRemaining - fudge,
15804                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15805     }
15806     if (CheckFlags()) return;
15807
15808     tickStartTM = now;
15809     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15810     StartClockTimer(intendedTickLength);
15811
15812     /* if the time remaining has fallen below the alarm threshold, sound the
15813      * alarm. if the alarm has sounded and (due to a takeback or time control
15814      * with increment) the time remaining has increased to a level above the
15815      * threshold, reset the alarm so it can sound again.
15816      */
15817
15818     if (appData.icsActive && appData.icsAlarm) {
15819
15820         /* make sure we are dealing with the user's clock */
15821         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15822                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15823            )) return;
15824
15825         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15826             alarmSounded = FALSE;
15827         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15828             PlayAlarmSound();
15829             alarmSounded = TRUE;
15830         }
15831     }
15832 }
15833
15834
15835 /* A player has just moved, so stop the previously running
15836    clock and (if in clock mode) start the other one.
15837    We redisplay both clocks in case we're in ICS mode, because
15838    ICS gives us an update to both clocks after every move.
15839    Note that this routine is called *after* forwardMostMove
15840    is updated, so the last fractional tick must be subtracted
15841    from the color that is *not* on move now.
15842 */
15843 void
15844 SwitchClocks(int newMoveNr)
15845 {
15846     long lastTickLength;
15847     TimeMark now;
15848     int flagged = FALSE;
15849
15850     GetTimeMark(&now);
15851
15852     if (StopClockTimer() && appData.clockMode) {
15853         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15854         if (!WhiteOnMove(forwardMostMove)) {
15855             if(blackNPS >= 0) lastTickLength = 0;
15856             blackTimeRemaining -= lastTickLength;
15857            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15858 //         if(pvInfoList[forwardMostMove].time == -1)
15859                  pvInfoList[forwardMostMove].time =               // use GUI time
15860                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15861         } else {
15862            if(whiteNPS >= 0) lastTickLength = 0;
15863            whiteTimeRemaining -= lastTickLength;
15864            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15865 //         if(pvInfoList[forwardMostMove].time == -1)
15866                  pvInfoList[forwardMostMove].time =
15867                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15868         }
15869         flagged = CheckFlags();
15870     }
15871     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15872     CheckTimeControl();
15873
15874     if (flagged || !appData.clockMode) return;
15875
15876     switch (gameMode) {
15877       case MachinePlaysBlack:
15878       case MachinePlaysWhite:
15879       case BeginningOfGame:
15880         if (pausing) return;
15881         break;
15882
15883       case EditGame:
15884       case PlayFromGameFile:
15885       case IcsExamining:
15886         return;
15887
15888       default:
15889         break;
15890     }
15891
15892     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15893         if(WhiteOnMove(forwardMostMove))
15894              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15895         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15896     }
15897
15898     tickStartTM = now;
15899     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15900       whiteTimeRemaining : blackTimeRemaining);
15901     StartClockTimer(intendedTickLength);
15902 }
15903
15904
15905 /* Stop both clocks */
15906 void
15907 StopClocks()
15908 {
15909     long lastTickLength;
15910     TimeMark now;
15911
15912     if (!StopClockTimer()) return;
15913     if (!appData.clockMode) return;
15914
15915     GetTimeMark(&now);
15916
15917     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15918     if (WhiteOnMove(forwardMostMove)) {
15919         if(whiteNPS >= 0) lastTickLength = 0;
15920         whiteTimeRemaining -= lastTickLength;
15921         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15922     } else {
15923         if(blackNPS >= 0) lastTickLength = 0;
15924         blackTimeRemaining -= lastTickLength;
15925         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15926     }
15927     CheckFlags();
15928 }
15929
15930 /* Start clock of player on move.  Time may have been reset, so
15931    if clock is already running, stop and restart it. */
15932 void
15933 StartClocks()
15934 {
15935     (void) StopClockTimer(); /* in case it was running already */
15936     DisplayBothClocks();
15937     if (CheckFlags()) return;
15938
15939     if (!appData.clockMode) return;
15940     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15941
15942     GetTimeMark(&tickStartTM);
15943     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15944       whiteTimeRemaining : blackTimeRemaining);
15945
15946    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15947     whiteNPS = blackNPS = -1;
15948     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15949        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15950         whiteNPS = first.nps;
15951     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15952        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15953         blackNPS = first.nps;
15954     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15955         whiteNPS = second.nps;
15956     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15957         blackNPS = second.nps;
15958     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15959
15960     StartClockTimer(intendedTickLength);
15961 }
15962
15963 char *
15964 TimeString(ms)
15965      long ms;
15966 {
15967     long second, minute, hour, day;
15968     char *sign = "";
15969     static char buf[32];
15970
15971     if (ms > 0 && ms <= 9900) {
15972       /* convert milliseconds to tenths, rounding up */
15973       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15974
15975       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15976       return buf;
15977     }
15978
15979     /* convert milliseconds to seconds, rounding up */
15980     /* use floating point to avoid strangeness of integer division
15981        with negative dividends on many machines */
15982     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15983
15984     if (second < 0) {
15985         sign = "-";
15986         second = -second;
15987     }
15988
15989     day = second / (60 * 60 * 24);
15990     second = second % (60 * 60 * 24);
15991     hour = second / (60 * 60);
15992     second = second % (60 * 60);
15993     minute = second / 60;
15994     second = second % 60;
15995
15996     if (day > 0)
15997       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15998               sign, day, hour, minute, second);
15999     else if (hour > 0)
16000       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16001     else
16002       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16003
16004     return buf;
16005 }
16006
16007
16008 /*
16009  * This is necessary because some C libraries aren't ANSI C compliant yet.
16010  */
16011 char *
16012 StrStr(string, match)
16013      char *string, *match;
16014 {
16015     int i, length;
16016
16017     length = strlen(match);
16018
16019     for (i = strlen(string) - length; i >= 0; i--, string++)
16020       if (!strncmp(match, string, length))
16021         return string;
16022
16023     return NULL;
16024 }
16025
16026 char *
16027 StrCaseStr(string, match)
16028      char *string, *match;
16029 {
16030     int i, j, length;
16031
16032     length = strlen(match);
16033
16034     for (i = strlen(string) - length; i >= 0; i--, string++) {
16035         for (j = 0; j < length; j++) {
16036             if (ToLower(match[j]) != ToLower(string[j]))
16037               break;
16038         }
16039         if (j == length) return string;
16040     }
16041
16042     return NULL;
16043 }
16044
16045 #ifndef _amigados
16046 int
16047 StrCaseCmp(s1, s2)
16048      char *s1, *s2;
16049 {
16050     char c1, c2;
16051
16052     for (;;) {
16053         c1 = ToLower(*s1++);
16054         c2 = ToLower(*s2++);
16055         if (c1 > c2) return 1;
16056         if (c1 < c2) return -1;
16057         if (c1 == NULLCHAR) return 0;
16058     }
16059 }
16060
16061
16062 int
16063 ToLower(c)
16064      int c;
16065 {
16066     return isupper(c) ? tolower(c) : c;
16067 }
16068
16069
16070 int
16071 ToUpper(c)
16072      int c;
16073 {
16074     return islower(c) ? toupper(c) : c;
16075 }
16076 #endif /* !_amigados    */
16077
16078 char *
16079 StrSave(s)
16080      char *s;
16081 {
16082   char *ret;
16083
16084   if ((ret = (char *) malloc(strlen(s) + 1)))
16085     {
16086       safeStrCpy(ret, s, strlen(s)+1);
16087     }
16088   return ret;
16089 }
16090
16091 char *
16092 StrSavePtr(s, savePtr)
16093      char *s, **savePtr;
16094 {
16095     if (*savePtr) {
16096         free(*savePtr);
16097     }
16098     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16099       safeStrCpy(*savePtr, s, strlen(s)+1);
16100     }
16101     return(*savePtr);
16102 }
16103
16104 char *
16105 PGNDate()
16106 {
16107     time_t clock;
16108     struct tm *tm;
16109     char buf[MSG_SIZ];
16110
16111     clock = time((time_t *)NULL);
16112     tm = localtime(&clock);
16113     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16114             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16115     return StrSave(buf);
16116 }
16117
16118
16119 char *
16120 PositionToFEN(move, overrideCastling)
16121      int move;
16122      char *overrideCastling;
16123 {
16124     int i, j, fromX, fromY, toX, toY;
16125     int whiteToPlay;
16126     char buf[MSG_SIZ];
16127     char *p, *q;
16128     int emptycount;
16129     ChessSquare piece;
16130
16131     whiteToPlay = (gameMode == EditPosition) ?
16132       !blackPlaysFirst : (move % 2 == 0);
16133     p = buf;
16134
16135     /* Piece placement data */
16136     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16137         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16138         emptycount = 0;
16139         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16140             if (boards[move][i][j] == EmptySquare) {
16141                 emptycount++;
16142             } else { ChessSquare piece = boards[move][i][j];
16143                 if (emptycount > 0) {
16144                     if(emptycount<10) /* [HGM] can be >= 10 */
16145                         *p++ = '0' + emptycount;
16146                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16147                     emptycount = 0;
16148                 }
16149                 if(PieceToChar(piece) == '+') {
16150                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16151                     *p++ = '+';
16152                     piece = (ChessSquare)(DEMOTED piece);
16153                 }
16154                 *p++ = PieceToChar(piece);
16155                 if(p[-1] == '~') {
16156                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16157                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16158                     *p++ = '~';
16159                 }
16160             }
16161         }
16162         if (emptycount > 0) {
16163             if(emptycount<10) /* [HGM] can be >= 10 */
16164                 *p++ = '0' + emptycount;
16165             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16166             emptycount = 0;
16167         }
16168         *p++ = '/';
16169     }
16170     *(p - 1) = ' ';
16171
16172     /* [HGM] print Crazyhouse or Shogi holdings */
16173     if( gameInfo.holdingsWidth ) {
16174         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16175         q = p;
16176         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16177             piece = boards[move][i][BOARD_WIDTH-1];
16178             if( piece != EmptySquare )
16179               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16180                   *p++ = PieceToChar(piece);
16181         }
16182         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16183             piece = boards[move][BOARD_HEIGHT-i-1][0];
16184             if( piece != EmptySquare )
16185               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16186                   *p++ = PieceToChar(piece);
16187         }
16188
16189         if( q == p ) *p++ = '-';
16190         *p++ = ']';
16191         *p++ = ' ';
16192     }
16193
16194     /* Active color */
16195     *p++ = whiteToPlay ? 'w' : 'b';
16196     *p++ = ' ';
16197
16198   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16199     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16200   } else {
16201   if(nrCastlingRights) {
16202      q = p;
16203      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16204        /* [HGM] write directly from rights */
16205            if(boards[move][CASTLING][2] != NoRights &&
16206               boards[move][CASTLING][0] != NoRights   )
16207                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16208            if(boards[move][CASTLING][2] != NoRights &&
16209               boards[move][CASTLING][1] != NoRights   )
16210                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16211            if(boards[move][CASTLING][5] != NoRights &&
16212               boards[move][CASTLING][3] != NoRights   )
16213                 *p++ = boards[move][CASTLING][3] + AAA;
16214            if(boards[move][CASTLING][5] != NoRights &&
16215               boards[move][CASTLING][4] != NoRights   )
16216                 *p++ = boards[move][CASTLING][4] + AAA;
16217      } else {
16218
16219         /* [HGM] write true castling rights */
16220         if( nrCastlingRights == 6 ) {
16221             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16222                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16223             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16224                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16225             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16226                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16227             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16228                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16229         }
16230      }
16231      if (q == p) *p++ = '-'; /* No castling rights */
16232      *p++ = ' ';
16233   }
16234
16235   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16236      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16237     /* En passant target square */
16238     if (move > backwardMostMove) {
16239         fromX = moveList[move - 1][0] - AAA;
16240         fromY = moveList[move - 1][1] - ONE;
16241         toX = moveList[move - 1][2] - AAA;
16242         toY = moveList[move - 1][3] - ONE;
16243         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16244             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16245             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16246             fromX == toX) {
16247             /* 2-square pawn move just happened */
16248             *p++ = toX + AAA;
16249             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16250         } else {
16251             *p++ = '-';
16252         }
16253     } else if(move == backwardMostMove) {
16254         // [HGM] perhaps we should always do it like this, and forget the above?
16255         if((signed char)boards[move][EP_STATUS] >= 0) {
16256             *p++ = boards[move][EP_STATUS] + AAA;
16257             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16258         } else {
16259             *p++ = '-';
16260         }
16261     } else {
16262         *p++ = '-';
16263     }
16264     *p++ = ' ';
16265   }
16266   }
16267
16268     /* [HGM] find reversible plies */
16269     {   int i = 0, j=move;
16270
16271         if (appData.debugMode) { int k;
16272             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16273             for(k=backwardMostMove; k<=forwardMostMove; k++)
16274                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16275
16276         }
16277
16278         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16279         if( j == backwardMostMove ) i += initialRulePlies;
16280         sprintf(p, "%d ", i);
16281         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16282     }
16283     /* Fullmove number */
16284     sprintf(p, "%d", (move / 2) + 1);
16285
16286     return StrSave(buf);
16287 }
16288
16289 Boolean
16290 ParseFEN(board, blackPlaysFirst, fen)
16291     Board board;
16292      int *blackPlaysFirst;
16293      char *fen;
16294 {
16295     int i, j;
16296     char *p, c;
16297     int emptycount;
16298     ChessSquare piece;
16299
16300     p = fen;
16301
16302     /* [HGM] by default clear Crazyhouse holdings, if present */
16303     if(gameInfo.holdingsWidth) {
16304        for(i=0; i<BOARD_HEIGHT; i++) {
16305            board[i][0]             = EmptySquare; /* black holdings */
16306            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16307            board[i][1]             = (ChessSquare) 0; /* black counts */
16308            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16309        }
16310     }
16311
16312     /* Piece placement data */
16313     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16314         j = 0;
16315         for (;;) {
16316             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16317                 if (*p == '/') p++;
16318                 emptycount = gameInfo.boardWidth - j;
16319                 while (emptycount--)
16320                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16321                 break;
16322 #if(BOARD_FILES >= 10)
16323             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16324                 p++; emptycount=10;
16325                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16326                 while (emptycount--)
16327                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16328 #endif
16329             } else if (isdigit(*p)) {
16330                 emptycount = *p++ - '0';
16331                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16332                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16333                 while (emptycount--)
16334                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16335             } else if (*p == '+' || isalpha(*p)) {
16336                 if (j >= gameInfo.boardWidth) return FALSE;
16337                 if(*p=='+') {
16338                     piece = CharToPiece(*++p);
16339                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16340                     piece = (ChessSquare) (PROMOTED piece ); p++;
16341                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16342                 } else piece = CharToPiece(*p++);
16343
16344                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16345                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16346                     piece = (ChessSquare) (PROMOTED piece);
16347                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16348                     p++;
16349                 }
16350                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16351             } else {
16352                 return FALSE;
16353             }
16354         }
16355     }
16356     while (*p == '/' || *p == ' ') p++;
16357
16358     /* [HGM] look for Crazyhouse holdings here */
16359     while(*p==' ') p++;
16360     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16361         if(*p == '[') p++;
16362         if(*p == '-' ) p++; /* empty holdings */ else {
16363             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16364             /* if we would allow FEN reading to set board size, we would   */
16365             /* have to add holdings and shift the board read so far here   */
16366             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16367                 p++;
16368                 if((int) piece >= (int) BlackPawn ) {
16369                     i = (int)piece - (int)BlackPawn;
16370                     i = PieceToNumber((ChessSquare)i);
16371                     if( i >= gameInfo.holdingsSize ) return FALSE;
16372                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16373                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16374                 } else {
16375                     i = (int)piece - (int)WhitePawn;
16376                     i = PieceToNumber((ChessSquare)i);
16377                     if( i >= gameInfo.holdingsSize ) return FALSE;
16378                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16379                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16380                 }
16381             }
16382         }
16383         if(*p == ']') p++;
16384     }
16385
16386     while(*p == ' ') p++;
16387
16388     /* Active color */
16389     c = *p++;
16390     if(appData.colorNickNames) {
16391       if( c == appData.colorNickNames[0] ) c = 'w'; else
16392       if( c == appData.colorNickNames[1] ) c = 'b';
16393     }
16394     switch (c) {
16395       case 'w':
16396         *blackPlaysFirst = FALSE;
16397         break;
16398       case 'b':
16399         *blackPlaysFirst = TRUE;
16400         break;
16401       default:
16402         return FALSE;
16403     }
16404
16405     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16406     /* return the extra info in global variiables             */
16407
16408     /* set defaults in case FEN is incomplete */
16409     board[EP_STATUS] = EP_UNKNOWN;
16410     for(i=0; i<nrCastlingRights; i++ ) {
16411         board[CASTLING][i] =
16412             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16413     }   /* assume possible unless obviously impossible */
16414     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16415     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16416     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16417                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16418     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16419     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16420     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16421                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16422     FENrulePlies = 0;
16423
16424     while(*p==' ') p++;
16425     if(nrCastlingRights) {
16426       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16427           /* castling indicator present, so default becomes no castlings */
16428           for(i=0; i<nrCastlingRights; i++ ) {
16429                  board[CASTLING][i] = NoRights;
16430           }
16431       }
16432       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16433              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16434              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16435              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16436         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16437
16438         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16439             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16440             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16441         }
16442         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16443             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16444         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16445                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16446         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16447                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16448         switch(c) {
16449           case'K':
16450               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16451               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16452               board[CASTLING][2] = whiteKingFile;
16453               break;
16454           case'Q':
16455               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16456               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16457               board[CASTLING][2] = whiteKingFile;
16458               break;
16459           case'k':
16460               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16461               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16462               board[CASTLING][5] = blackKingFile;
16463               break;
16464           case'q':
16465               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16466               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16467               board[CASTLING][5] = blackKingFile;
16468           case '-':
16469               break;
16470           default: /* FRC castlings */
16471               if(c >= 'a') { /* black rights */
16472                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16473                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16474                   if(i == BOARD_RGHT) break;
16475                   board[CASTLING][5] = i;
16476                   c -= AAA;
16477                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16478                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16479                   if(c > i)
16480                       board[CASTLING][3] = c;
16481                   else
16482                       board[CASTLING][4] = c;
16483               } else { /* white rights */
16484                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16485                     if(board[0][i] == WhiteKing) break;
16486                   if(i == BOARD_RGHT) break;
16487                   board[CASTLING][2] = i;
16488                   c -= AAA - 'a' + 'A';
16489                   if(board[0][c] >= WhiteKing) break;
16490                   if(c > i)
16491                       board[CASTLING][0] = c;
16492                   else
16493                       board[CASTLING][1] = c;
16494               }
16495         }
16496       }
16497       for(i=0; i<nrCastlingRights; i++)
16498         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16499     if (appData.debugMode) {
16500         fprintf(debugFP, "FEN castling rights:");
16501         for(i=0; i<nrCastlingRights; i++)
16502         fprintf(debugFP, " %d", board[CASTLING][i]);
16503         fprintf(debugFP, "\n");
16504     }
16505
16506       while(*p==' ') p++;
16507     }
16508
16509     /* read e.p. field in games that know e.p. capture */
16510     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16511        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16512       if(*p=='-') {
16513         p++; board[EP_STATUS] = EP_NONE;
16514       } else {
16515          char c = *p++ - AAA;
16516
16517          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16518          if(*p >= '0' && *p <='9') p++;
16519          board[EP_STATUS] = c;
16520       }
16521     }
16522
16523
16524     if(sscanf(p, "%d", &i) == 1) {
16525         FENrulePlies = i; /* 50-move ply counter */
16526         /* (The move number is still ignored)    */
16527     }
16528
16529     return TRUE;
16530 }
16531
16532 void
16533 EditPositionPasteFEN(char *fen)
16534 {
16535   if (fen != NULL) {
16536     Board initial_position;
16537
16538     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16539       DisplayError(_("Bad FEN position in clipboard"), 0);
16540       return ;
16541     } else {
16542       int savedBlackPlaysFirst = blackPlaysFirst;
16543       EditPositionEvent();
16544       blackPlaysFirst = savedBlackPlaysFirst;
16545       CopyBoard(boards[0], initial_position);
16546       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16547       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16548       DisplayBothClocks();
16549       DrawPosition(FALSE, boards[currentMove]);
16550     }
16551   }
16552 }
16553
16554 static char cseq[12] = "\\   ";
16555
16556 Boolean set_cont_sequence(char *new_seq)
16557 {
16558     int len;
16559     Boolean ret;
16560
16561     // handle bad attempts to set the sequence
16562         if (!new_seq)
16563                 return 0; // acceptable error - no debug
16564
16565     len = strlen(new_seq);
16566     ret = (len > 0) && (len < sizeof(cseq));
16567     if (ret)
16568       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16569     else if (appData.debugMode)
16570       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16571     return ret;
16572 }
16573
16574 /*
16575     reformat a source message so words don't cross the width boundary.  internal
16576     newlines are not removed.  returns the wrapped size (no null character unless
16577     included in source message).  If dest is NULL, only calculate the size required
16578     for the dest buffer.  lp argument indicats line position upon entry, and it's
16579     passed back upon exit.
16580 */
16581 int wrap(char *dest, char *src, int count, int width, int *lp)
16582 {
16583     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16584
16585     cseq_len = strlen(cseq);
16586     old_line = line = *lp;
16587     ansi = len = clen = 0;
16588
16589     for (i=0; i < count; i++)
16590     {
16591         if (src[i] == '\033')
16592             ansi = 1;
16593
16594         // if we hit the width, back up
16595         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16596         {
16597             // store i & len in case the word is too long
16598             old_i = i, old_len = len;
16599
16600             // find the end of the last word
16601             while (i && src[i] != ' ' && src[i] != '\n')
16602             {
16603                 i--;
16604                 len--;
16605             }
16606
16607             // word too long?  restore i & len before splitting it
16608             if ((old_i-i+clen) >= width)
16609             {
16610                 i = old_i;
16611                 len = old_len;
16612             }
16613
16614             // extra space?
16615             if (i && src[i-1] == ' ')
16616                 len--;
16617
16618             if (src[i] != ' ' && src[i] != '\n')
16619             {
16620                 i--;
16621                 if (len)
16622                     len--;
16623             }
16624
16625             // now append the newline and continuation sequence
16626             if (dest)
16627                 dest[len] = '\n';
16628             len++;
16629             if (dest)
16630                 strncpy(dest+len, cseq, cseq_len);
16631             len += cseq_len;
16632             line = cseq_len;
16633             clen = cseq_len;
16634             continue;
16635         }
16636
16637         if (dest)
16638             dest[len] = src[i];
16639         len++;
16640         if (!ansi)
16641             line++;
16642         if (src[i] == '\n')
16643             line = 0;
16644         if (src[i] == 'm')
16645             ansi = 0;
16646     }
16647     if (dest && appData.debugMode)
16648     {
16649         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16650             count, width, line, len, *lp);
16651         show_bytes(debugFP, src, count);
16652         fprintf(debugFP, "\ndest: ");
16653         show_bytes(debugFP, dest, len);
16654         fprintf(debugFP, "\n");
16655     }
16656     *lp = dest ? line : old_line;
16657
16658     return len;
16659 }
16660
16661 // [HGM] vari: routines for shelving variations
16662 Boolean modeRestore = FALSE;
16663
16664 void
16665 PushInner(int firstMove, int lastMove)
16666 {
16667         int i, j, nrMoves = lastMove - firstMove;
16668
16669         // push current tail of game on stack
16670         savedResult[storedGames] = gameInfo.result;
16671         savedDetails[storedGames] = gameInfo.resultDetails;
16672         gameInfo.resultDetails = NULL;
16673         savedFirst[storedGames] = firstMove;
16674         savedLast [storedGames] = lastMove;
16675         savedFramePtr[storedGames] = framePtr;
16676         framePtr -= nrMoves; // reserve space for the boards
16677         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16678             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16679             for(j=0; j<MOVE_LEN; j++)
16680                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16681             for(j=0; j<2*MOVE_LEN; j++)
16682                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16683             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16684             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16685             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16686             pvInfoList[firstMove+i-1].depth = 0;
16687             commentList[framePtr+i] = commentList[firstMove+i];
16688             commentList[firstMove+i] = NULL;
16689         }
16690
16691         storedGames++;
16692         forwardMostMove = firstMove; // truncate game so we can start variation
16693 }
16694
16695 void
16696 PushTail(int firstMove, int lastMove)
16697 {
16698         if(appData.icsActive) { // only in local mode
16699                 forwardMostMove = currentMove; // mimic old ICS behavior
16700                 return;
16701         }
16702         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16703
16704         PushInner(firstMove, lastMove);
16705         if(storedGames == 1) GreyRevert(FALSE);
16706         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16707 }
16708
16709 void
16710 PopInner(Boolean annotate)
16711 {
16712         int i, j, nrMoves;
16713         char buf[8000], moveBuf[20];
16714
16715         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16716         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16717         nrMoves = savedLast[storedGames] - currentMove;
16718         if(annotate) {
16719                 int cnt = 10;
16720                 if(!WhiteOnMove(currentMove))
16721                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16722                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16723                 for(i=currentMove; i<forwardMostMove; i++) {
16724                         if(WhiteOnMove(i))
16725                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16726                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16727                         strcat(buf, moveBuf);
16728                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16729                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16730                 }
16731                 strcat(buf, ")");
16732         }
16733         for(i=1; i<=nrMoves; i++) { // copy last variation back
16734             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16735             for(j=0; j<MOVE_LEN; j++)
16736                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16737             for(j=0; j<2*MOVE_LEN; j++)
16738                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16739             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16740             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16741             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16742             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16743             commentList[currentMove+i] = commentList[framePtr+i];
16744             commentList[framePtr+i] = NULL;
16745         }
16746         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16747         framePtr = savedFramePtr[storedGames];
16748         gameInfo.result = savedResult[storedGames];
16749         if(gameInfo.resultDetails != NULL) {
16750             free(gameInfo.resultDetails);
16751       }
16752         gameInfo.resultDetails = savedDetails[storedGames];
16753         forwardMostMove = currentMove + nrMoves;
16754 }
16755
16756 Boolean
16757 PopTail(Boolean annotate)
16758 {
16759         if(appData.icsActive) return FALSE; // only in local mode
16760         if(!storedGames) return FALSE; // sanity
16761         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16762
16763         PopInner(annotate);
16764         if(currentMove < forwardMostMove) ForwardEvent(); else
16765         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16766
16767         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16768         return TRUE;
16769 }
16770
16771 void
16772 CleanupTail()
16773 {       // remove all shelved variations
16774         int i;
16775         for(i=0; i<storedGames; i++) {
16776             if(savedDetails[i])
16777                 free(savedDetails[i]);
16778             savedDetails[i] = NULL;
16779         }
16780         for(i=framePtr; i<MAX_MOVES; i++) {
16781                 if(commentList[i]) free(commentList[i]);
16782                 commentList[i] = NULL;
16783         }
16784         framePtr = MAX_MOVES-1;
16785         storedGames = 0;
16786 }
16787
16788 void
16789 LoadVariation(int index, char *text)
16790 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16791         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16792         int level = 0, move;
16793
16794         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16795         // first find outermost bracketing variation
16796         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16797             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16798                 if(*p == '{') wait = '}'; else
16799                 if(*p == '[') wait = ']'; else
16800                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16801                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16802             }
16803             if(*p == wait) wait = NULLCHAR; // closing ]} found
16804             p++;
16805         }
16806         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16807         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16808         end[1] = NULLCHAR; // clip off comment beyond variation
16809         ToNrEvent(currentMove-1);
16810         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16811         // kludge: use ParsePV() to append variation to game
16812         move = currentMove;
16813         ParsePV(start, TRUE, TRUE);
16814         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16815         ClearPremoveHighlights();
16816         CommentPopDown();
16817         ToNrEvent(currentMove+1);
16818 }
16819