Fix position loading in tourneys
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
172                                                                                 Board board));
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176                    /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188                            char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190                         int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
197
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397   case VariantGrand:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 Boolean shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 ChessSquare GrandArray[2][BOARD_FILES] = {
602     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
603         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
604     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
605         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
606 };
607
608 #ifdef GOTHIC
609 ChessSquare GothicArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
611         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
613         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !GOTHIC
616 #define GothicArray CapablancaArray
617 #endif // !GOTHIC
618
619 #ifdef FALCON
620 ChessSquare FalconArray[2][BOARD_FILES] = {
621     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
622         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
624         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
625 };
626 #else // !FALCON
627 #define FalconArray CapablancaArray
628 #endif // !FALCON
629
630 #else // !(BOARD_FILES>=10)
631 #define XiangqiPosition FIDEArray
632 #define CapablancaArray FIDEArray
633 #define GothicArray FIDEArray
634 #define GreatArray FIDEArray
635 #endif // !(BOARD_FILES>=10)
636
637 #if (BOARD_FILES>=12)
638 ChessSquare CourierArray[2][BOARD_FILES] = {
639     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
640         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
641     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
642         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
643 };
644 #else // !(BOARD_FILES>=12)
645 #define CourierArray CapablancaArray
646 #endif // !(BOARD_FILES>=12)
647
648
649 Board initialPosition;
650
651
652 /* Convert str to a rating. Checks for special cases of "----",
653
654    "++++", etc. Also strips ()'s */
655 int
656 string_to_rating(str)
657   char *str;
658 {
659   while(*str && !isdigit(*str)) ++str;
660   if (!*str)
661     return 0;   /* One of the special "no rating" cases */
662   else
663     return atoi(str);
664 }
665
666 void
667 ClearProgramStats()
668 {
669     /* Init programStats */
670     programStats.movelist[0] = 0;
671     programStats.depth = 0;
672     programStats.nr_moves = 0;
673     programStats.moves_left = 0;
674     programStats.nodes = 0;
675     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
676     programStats.score = 0;
677     programStats.got_only_move = 0;
678     programStats.got_fail = 0;
679     programStats.line_is_book = 0;
680 }
681
682 void
683 CommonEngineInit()
684 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
685     if (appData.firstPlaysBlack) {
686         first.twoMachinesColor = "black\n";
687         second.twoMachinesColor = "white\n";
688     } else {
689         first.twoMachinesColor = "white\n";
690         second.twoMachinesColor = "black\n";
691     }
692
693     first.other = &second;
694     second.other = &first;
695
696     { float norm = 1;
697         if(appData.timeOddsMode) {
698             norm = appData.timeOdds[0];
699             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
700         }
701         first.timeOdds  = appData.timeOdds[0]/norm;
702         second.timeOdds = appData.timeOdds[1]/norm;
703     }
704
705     if(programVersion) free(programVersion);
706     if (appData.noChessProgram) {
707         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
708         sprintf(programVersion, "%s", PACKAGE_STRING);
709     } else {
710       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
711       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
712       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
713     }
714 }
715
716 void
717 UnloadEngine(ChessProgramState *cps)
718 {
719         /* Kill off first chess program */
720         if (cps->isr != NULL)
721           RemoveInputSource(cps->isr);
722         cps->isr = NULL;
723
724         if (cps->pr != NoProc) {
725             ExitAnalyzeMode();
726             DoSleep( appData.delayBeforeQuit );
727             SendToProgram("quit\n", cps);
728             DoSleep( appData.delayAfterQuit );
729             DestroyChildProcess(cps->pr, cps->useSigterm);
730         }
731         cps->pr = NoProc;
732         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
733 }
734
735 void
736 ClearOptions(ChessProgramState *cps)
737 {
738     int i;
739     cps->nrOptions = cps->comboCnt = 0;
740     for(i=0; i<MAX_OPTIONS; i++) {
741         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
742         cps->option[i].textValue = 0;
743     }
744 }
745
746 char *engineNames[] = {
747 "first",
748 "second"
749 };
750
751 void
752 InitEngine(ChessProgramState *cps, int n)
753 {   // [HGM] all engine initialiation put in a function that does one engine
754
755     ClearOptions(cps);
756
757     cps->which = engineNames[n];
758     cps->maybeThinking = FALSE;
759     cps->pr = NoProc;
760     cps->isr = NULL;
761     cps->sendTime = 2;
762     cps->sendDrawOffers = 1;
763
764     cps->program = appData.chessProgram[n];
765     cps->host = appData.host[n];
766     cps->dir = appData.directory[n];
767     cps->initString = appData.engInitString[n];
768     cps->computerString = appData.computerString[n];
769     cps->useSigint  = TRUE;
770     cps->useSigterm = TRUE;
771     cps->reuse = appData.reuse[n];
772     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
773     cps->useSetboard = FALSE;
774     cps->useSAN = FALSE;
775     cps->usePing = FALSE;
776     cps->lastPing = 0;
777     cps->lastPong = 0;
778     cps->usePlayother = FALSE;
779     cps->useColors = TRUE;
780     cps->useUsermove = FALSE;
781     cps->sendICS = FALSE;
782     cps->sendName = appData.icsActive;
783     cps->sdKludge = FALSE;
784     cps->stKludge = FALSE;
785     TidyProgramName(cps->program, cps->host, cps->tidy);
786     cps->matchWins = 0;
787     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
788     cps->analysisSupport = 2; /* detect */
789     cps->analyzing = FALSE;
790     cps->initDone = FALSE;
791
792     /* New features added by Tord: */
793     cps->useFEN960 = FALSE;
794     cps->useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     cps->fenOverride  = appData.fenOverride[n];
797
798     /* [HGM] time odds: set factor for each machine */
799     cps->timeOdds  = appData.timeOdds[n];
800
801     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802     cps->accumulateTC = appData.accumulateTC[n];
803     cps->maxNrOfSessions = 1;
804
805     /* [HGM] debug */
806     cps->debug = FALSE;
807
808     cps->supportsNPS = UNKNOWN;
809     cps->memSize = FALSE;
810     cps->maxCores = FALSE;
811     cps->egtFormats[0] = NULLCHAR;
812
813     /* [HGM] options */
814     cps->optionSettings  = appData.engOptions[n];
815
816     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817     cps->isUCI = appData.isUCI[n]; /* [AS] */
818     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819
820     if (appData.protocolVersion[n] > PROTOVER
821         || appData.protocolVersion[n] < 1)
822       {
823         char buf[MSG_SIZ];
824         int len;
825
826         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827                        appData.protocolVersion[n]);
828         if( (len > MSG_SIZ) && appData.debugMode )
829           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830
831         DisplayFatalError(buf, 0, 2);
832       }
833     else
834       {
835         cps->protocolVersion = appData.protocolVersion[n];
836       }
837
838     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine(ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] = 
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
883
884 void
885 Load(ChessProgramState *cps, int i)
886 {
887     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
888     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
889         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
890         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
891         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
892         ParseArgsFromString(buf);
893         SwapEngines(i);
894         ReplaceEngine(cps, i);
895         return;
896     }
897     p = engineName;
898     while(q = strchr(p, SLASH)) p = q+1;
899     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
900     if(engineDir[0] != NULLCHAR)
901         appData.directory[i] = engineDir;
902     else if(p != engineName) { // derive directory from engine path, when not given
903         p[-1] = 0;
904         appData.directory[i] = strdup(engineName);
905         p[-1] = SLASH;
906     } else appData.directory[i] = ".";
907     if(params[0]) {
908         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
909         snprintf(command, MSG_SIZ, "%s %s", p, params);
910         p = command;
911     }
912     appData.chessProgram[i] = strdup(p);
913     appData.isUCI[i] = isUCI;
914     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
915     appData.hasOwnBookUCI[i] = hasBook;
916     if(!nickName[0]) useNick = FALSE;
917     if(useNick) ASSIGN(appData.pgnName[i], nickName);
918     if(addToList) {
919         int len;
920         char quote;
921         q = firstChessProgramNames;
922         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
923         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
924         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
925                         quote, p, quote, appData.directory[i], 
926                         useNick ? " -fn \"" : "",
927                         useNick ? nickName : "",
928                         useNick ? "\"" : "",
929                         v1 ? " -firstProtocolVersion 1" : "",
930                         hasBook ? "" : " -fNoOwnBookUCI",
931                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
932                         storeVariant ? " -variant " : "",
933                         storeVariant ? VariantName(gameInfo.variant) : "");
934         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
935         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
936         if(q)   free(q);
937     }
938     ReplaceEngine(cps, i);
939 }
940
941 void
942 InitTimeControls()
943 {
944     int matched, min, sec;
945     /*
946      * Parse timeControl resource
947      */
948     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
949                           appData.movesPerSession)) {
950         char buf[MSG_SIZ];
951         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
952         DisplayFatalError(buf, 0, 2);
953     }
954
955     /*
956      * Parse searchTime resource
957      */
958     if (*appData.searchTime != NULLCHAR) {
959         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
960         if (matched == 1) {
961             searchTime = min * 60;
962         } else if (matched == 2) {
963             searchTime = min * 60 + sec;
964         } else {
965             char buf[MSG_SIZ];
966             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
967             DisplayFatalError(buf, 0, 2);
968         }
969     }
970 }
971
972 void
973 InitBackEnd1()
974 {
975
976     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
977     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
978
979     GetTimeMark(&programStartTime);
980     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
981     appData.seedBase = random() + (random()<<15);
982     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
983
984     ClearProgramStats();
985     programStats.ok_to_send = 1;
986     programStats.seen_stat = 0;
987
988     /*
989      * Initialize game list
990      */
991     ListNew(&gameList);
992
993
994     /*
995      * Internet chess server status
996      */
997     if (appData.icsActive) {
998         appData.matchMode = FALSE;
999         appData.matchGames = 0;
1000 #if ZIPPY
1001         appData.noChessProgram = !appData.zippyPlay;
1002 #else
1003         appData.zippyPlay = FALSE;
1004         appData.zippyTalk = FALSE;
1005         appData.noChessProgram = TRUE;
1006 #endif
1007         if (*appData.icsHelper != NULLCHAR) {
1008             appData.useTelnet = TRUE;
1009             appData.telnetProgram = appData.icsHelper;
1010         }
1011     } else {
1012         appData.zippyTalk = appData.zippyPlay = FALSE;
1013     }
1014
1015     /* [AS] Initialize pv info list [HGM] and game state */
1016     {
1017         int i, j;
1018
1019         for( i=0; i<=framePtr; i++ ) {
1020             pvInfoList[i].depth = -1;
1021             boards[i][EP_STATUS] = EP_NONE;
1022             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1023         }
1024     }
1025
1026     InitTimeControls();
1027
1028     /* [AS] Adjudication threshold */
1029     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1030
1031     InitEngine(&first, 0);
1032     InitEngine(&second, 1);
1033     CommonEngineInit();
1034
1035     pairing.which = "pairing"; // pairing engine
1036     pairing.pr = NoProc;
1037     pairing.isr = NULL;
1038     pairing.program = appData.pairingEngine;
1039     pairing.host = "localhost";
1040     pairing.dir = ".";
1041
1042     if (appData.icsActive) {
1043         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1044     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1045         appData.clockMode = FALSE;
1046         first.sendTime = second.sendTime = 0;
1047     }
1048
1049 #if ZIPPY
1050     /* Override some settings from environment variables, for backward
1051        compatibility.  Unfortunately it's not feasible to have the env
1052        vars just set defaults, at least in xboard.  Ugh.
1053     */
1054     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1055       ZippyInit();
1056     }
1057 #endif
1058
1059     if (!appData.icsActive) {
1060       char buf[MSG_SIZ];
1061       int len;
1062
1063       /* Check for variants that are supported only in ICS mode,
1064          or not at all.  Some that are accepted here nevertheless
1065          have bugs; see comments below.
1066       */
1067       VariantClass variant = StringToVariant(appData.variant);
1068       switch (variant) {
1069       case VariantBughouse:     /* need four players and two boards */
1070       case VariantKriegspiel:   /* need to hide pieces and move details */
1071         /* case VariantFischeRandom: (Fabien: moved below) */
1072         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1073         if( (len > MSG_SIZ) && appData.debugMode )
1074           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1075
1076         DisplayFatalError(buf, 0, 2);
1077         return;
1078
1079       case VariantUnknown:
1080       case VariantLoadable:
1081       case Variant29:
1082       case Variant30:
1083       case Variant31:
1084       case Variant32:
1085       case Variant33:
1086       case Variant34:
1087       case Variant35:
1088       case Variant36:
1089       default:
1090         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1091         if( (len > MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1098       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1099       case VariantGothic:     /* [HGM] should work */
1100       case VariantCapablanca: /* [HGM] should work */
1101       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1102       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1103       case VariantKnightmate: /* [HGM] should work */
1104       case VariantCylinder:   /* [HGM] untested */
1105       case VariantFalcon:     /* [HGM] untested */
1106       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1107                                  offboard interposition not understood */
1108       case VariantNormal:     /* definitely works! */
1109       case VariantWildCastle: /* pieces not automatically shuffled */
1110       case VariantNoCastle:   /* pieces not automatically shuffled */
1111       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1112       case VariantLosers:     /* should work except for win condition,
1113                                  and doesn't know captures are mandatory */
1114       case VariantSuicide:    /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantGiveaway:   /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantTwoKings:   /* should work */
1119       case VariantAtomic:     /* should work except for win condition */
1120       case Variant3Check:     /* should work except for win condition */
1121       case VariantShatranj:   /* should work except for all win conditions */
1122       case VariantMakruk:     /* should work except for draw countdown */
1123       case VariantBerolina:   /* might work if TestLegality is off */
1124       case VariantCapaRandom: /* should work */
1125       case VariantJanus:      /* should work */
1126       case VariantSuper:      /* experimental */
1127       case VariantGreat:      /* experimental, requires legality testing to be off */
1128       case VariantSChess:     /* S-Chess, should work */
1129       case VariantGrand:      /* should work */
1130       case VariantSpartan:    /* should work */
1131         break;
1132       }
1133     }
1134
1135 }
1136
1137 int NextIntegerFromString( char ** str, long * value )
1138 {
1139     int result = -1;
1140     char * s = *str;
1141
1142     while( *s == ' ' || *s == '\t' ) {
1143         s++;
1144     }
1145
1146     *value = 0;
1147
1148     if( *s >= '0' && *s <= '9' ) {
1149         while( *s >= '0' && *s <= '9' ) {
1150             *value = *value * 10 + (*s - '0');
1151             s++;
1152         }
1153
1154         result = 0;
1155     }
1156
1157     *str = s;
1158
1159     return result;
1160 }
1161
1162 int NextTimeControlFromString( char ** str, long * value )
1163 {
1164     long temp;
1165     int result = NextIntegerFromString( str, &temp );
1166
1167     if( result == 0 ) {
1168         *value = temp * 60; /* Minutes */
1169         if( **str == ':' ) {
1170             (*str)++;
1171             result = NextIntegerFromString( str, &temp );
1172             *value += temp; /* Seconds */
1173         }
1174     }
1175
1176     return result;
1177 }
1178
1179 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1180 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1181     int result = -1, type = 0; long temp, temp2;
1182
1183     if(**str != ':') return -1; // old params remain in force!
1184     (*str)++;
1185     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1186     if( NextIntegerFromString( str, &temp ) ) return -1;
1187     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1188
1189     if(**str != '/') {
1190         /* time only: incremental or sudden-death time control */
1191         if(**str == '+') { /* increment follows; read it */
1192             (*str)++;
1193             if(**str == '!') type = *(*str)++; // Bronstein TC
1194             if(result = NextIntegerFromString( str, &temp2)) return -1;
1195             *inc = temp2 * 1000;
1196             if(**str == '.') { // read fraction of increment
1197                 char *start = ++(*str);
1198                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1199                 temp2 *= 1000;
1200                 while(start++ < *str) temp2 /= 10;
1201                 *inc += temp2;
1202             }
1203         } else *inc = 0;
1204         *moves = 0; *tc = temp * 1000; *incType = type;
1205         return 0;
1206     }
1207
1208     (*str)++; /* classical time control */
1209     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210
1211     if(result == 0) {
1212         *moves = temp;
1213         *tc    = temp2 * 1000;
1214         *inc   = 0;
1215         *incType = type;
1216     }
1217     return result;
1218 }
1219
1220 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1221 {   /* [HGM] get time to add from the multi-session time-control string */
1222     int incType, moves=1; /* kludge to force reading of first session */
1223     long time, increment;
1224     char *s = tcString;
1225
1226     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1227     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1228     do {
1229         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1230         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1231         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1232         if(movenr == -1) return time;    /* last move before new session     */
1233         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1234         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1235         if(!moves) return increment;     /* current session is incremental   */
1236         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1237     } while(movenr >= -1);               /* try again for next session       */
1238
1239     return 0; // no new time quota on this move
1240 }
1241
1242 int
1243 ParseTimeControl(tc, ti, mps)
1244      char *tc;
1245      float ti;
1246      int mps;
1247 {
1248   long tc1;
1249   long tc2;
1250   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1251   int min, sec=0;
1252
1253   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1254   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1255       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1256   if(ti > 0) {
1257
1258     if(mps)
1259       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1260     else 
1261       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1262   } else {
1263     if(mps)
1264       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1265     else 
1266       snprintf(buf, MSG_SIZ, ":%s", mytc);
1267   }
1268   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1269   
1270   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1271     return FALSE;
1272   }
1273
1274   if( *tc == '/' ) {
1275     /* Parse second time control */
1276     tc++;
1277
1278     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1279       return FALSE;
1280     }
1281
1282     if( tc2 == 0 ) {
1283       return FALSE;
1284     }
1285
1286     timeControl_2 = tc2 * 1000;
1287   }
1288   else {
1289     timeControl_2 = 0;
1290   }
1291
1292   if( tc1 == 0 ) {
1293     return FALSE;
1294   }
1295
1296   timeControl = tc1 * 1000;
1297
1298   if (ti >= 0) {
1299     timeIncrement = ti * 1000;  /* convert to ms */
1300     movesPerSession = 0;
1301   } else {
1302     timeIncrement = 0;
1303     movesPerSession = mps;
1304   }
1305   return TRUE;
1306 }
1307
1308 void
1309 InitBackEnd2()
1310 {
1311     if (appData.debugMode) {
1312         fprintf(debugFP, "%s\n", programVersion);
1313     }
1314
1315     set_cont_sequence(appData.wrapContSeq);
1316     if (appData.matchGames > 0) {
1317         appData.matchMode = TRUE;
1318     } else if (appData.matchMode) {
1319         appData.matchGames = 1;
1320     }
1321     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1322         appData.matchGames = appData.sameColorGames;
1323     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1324         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1325         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1326     }
1327     Reset(TRUE, FALSE);
1328     if (appData.noChessProgram || first.protocolVersion == 1) {
1329       InitBackEnd3();
1330     } else {
1331       /* kludge: allow timeout for initial "feature" commands */
1332       FreezeUI();
1333       DisplayMessage("", _("Starting chess program"));
1334       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1335     }
1336 }
1337
1338 int
1339 CalculateIndex(int index, int gameNr)
1340 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1341     int res;
1342     if(index > 0) return index; // fixed nmber
1343     if(index == 0) return 1;
1344     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1345     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1346     return res;
1347 }
1348
1349 int
1350 LoadGameOrPosition(int gameNr)
1351 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1352     if (*appData.loadGameFile != NULLCHAR) {
1353         if (!LoadGameFromFile(appData.loadGameFile,
1354                 CalculateIndex(appData.loadGameIndex, gameNr),
1355                               appData.loadGameFile, FALSE)) {
1356             DisplayFatalError(_("Bad game file"), 0, 1);
1357             return 0;
1358         }
1359     } else if (*appData.loadPositionFile != NULLCHAR) {
1360         if (!LoadPositionFromFile(appData.loadPositionFile,
1361                 CalculateIndex(appData.loadPositionIndex, gameNr),
1362                                   appData.loadPositionFile)) {
1363             DisplayFatalError(_("Bad position file"), 0, 1);
1364             return 0;
1365         }
1366     }
1367     return 1;
1368 }
1369
1370 void
1371 ReserveGame(int gameNr, char resChar)
1372 {
1373     FILE *tf = fopen(appData.tourneyFile, "r+");
1374     char *p, *q, c, buf[MSG_SIZ];
1375     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1376     safeStrCpy(buf, lastMsg, MSG_SIZ);
1377     DisplayMessage(_("Pick new game"), "");
1378     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1379     ParseArgsFromFile(tf);
1380     p = q = appData.results;
1381     if(appData.debugMode) {
1382       char *r = appData.participants;
1383       fprintf(debugFP, "results = '%s'\n", p);
1384       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1385       fprintf(debugFP, "\n");
1386     }
1387     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1388     nextGame = q - p;
1389     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1390     safeStrCpy(q, p, strlen(p) + 2);
1391     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1392     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1393     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1394         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1395         q[nextGame] = '*';
1396     }
1397     fseek(tf, -(strlen(p)+4), SEEK_END);
1398     c = fgetc(tf);
1399     if(c != '"') // depending on DOS or Unix line endings we can be one off
1400          fseek(tf, -(strlen(p)+2), SEEK_END);
1401     else fseek(tf, -(strlen(p)+3), SEEK_END);
1402     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1403     DisplayMessage(buf, "");
1404     free(p); appData.results = q;
1405     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1406        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1407         UnloadEngine(&first);  // next game belongs to other pairing;
1408         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1409     }
1410 }
1411
1412 void
1413 MatchEvent(int mode)
1414 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1415         int dummy;
1416         if(matchMode) { // already in match mode: switch it off
1417             abortMatch = TRUE;
1418             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1419             return;
1420         }
1421 //      if(gameMode != BeginningOfGame) {
1422 //          DisplayError(_("You can only start a match from the initial position."), 0);
1423 //          return;
1424 //      }
1425         abortMatch = FALSE;
1426         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1427         /* Set up machine vs. machine match */
1428         nextGame = 0;
1429         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1430         if(appData.tourneyFile[0]) {
1431             ReserveGame(-1, 0);
1432             if(nextGame > appData.matchGames) {
1433                 char buf[MSG_SIZ];
1434                 if(strchr(appData.results, '*') == NULL) {
1435                     FILE *f;
1436                     appData.tourneyCycles++;
1437                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1438                         fclose(f);
1439                         NextTourneyGame(-1, &dummy);
1440                         ReserveGame(-1, 0);
1441                         if(nextGame <= appData.matchGames) {
1442                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1443                             matchMode = mode;
1444                             ScheduleDelayedEvent(NextMatchGame, 10000);
1445                             return;
1446                         }
1447                     }
1448                 }
1449                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1450                 DisplayError(buf, 0);
1451                 appData.tourneyFile[0] = 0;
1452                 return;
1453             }
1454         } else
1455         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1456             DisplayFatalError(_("Can't have a match with no chess programs"),
1457                               0, 2);
1458             return;
1459         }
1460         matchMode = mode;
1461         matchGame = roundNr = 1;
1462         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1463         NextMatchGame();
1464 }
1465
1466 void
1467 InitBackEnd3 P((void))
1468 {
1469     GameMode initialMode;
1470     char buf[MSG_SIZ];
1471     int err, len;
1472
1473     InitChessProgram(&first, startedFromSetupPosition);
1474
1475     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1476         free(programVersion);
1477         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1478         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1479     }
1480
1481     if (appData.icsActive) {
1482 #ifdef WIN32
1483         /* [DM] Make a console window if needed [HGM] merged ifs */
1484         ConsoleCreate();
1485 #endif
1486         err = establish();
1487         if (err != 0)
1488           {
1489             if (*appData.icsCommPort != NULLCHAR)
1490               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1491                              appData.icsCommPort);
1492             else
1493               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1494                         appData.icsHost, appData.icsPort);
1495
1496             if( (len > MSG_SIZ) && appData.debugMode )
1497               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498
1499             DisplayFatalError(buf, err, 1);
1500             return;
1501         }
1502         SetICSMode();
1503         telnetISR =
1504           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1505         fromUserISR =
1506           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1507         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1508             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1509     } else if (appData.noChessProgram) {
1510         SetNCPMode();
1511     } else {
1512         SetGNUMode();
1513     }
1514
1515     if (*appData.cmailGameName != NULLCHAR) {
1516         SetCmailMode();
1517         OpenLoopback(&cmailPR);
1518         cmailISR =
1519           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1520     }
1521
1522     ThawUI();
1523     DisplayMessage("", "");
1524     if (StrCaseCmp(appData.initialMode, "") == 0) {
1525       initialMode = BeginningOfGame;
1526       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1527         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1528         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1529         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1530         ModeHighlight();
1531       }
1532     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1533       initialMode = TwoMachinesPlay;
1534     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1535       initialMode = AnalyzeFile;
1536     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1537       initialMode = AnalyzeMode;
1538     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1539       initialMode = MachinePlaysWhite;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1541       initialMode = MachinePlaysBlack;
1542     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1543       initialMode = EditGame;
1544     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1545       initialMode = EditPosition;
1546     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1547       initialMode = Training;
1548     } else {
1549       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1550       if( (len > MSG_SIZ) && appData.debugMode )
1551         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1552
1553       DisplayFatalError(buf, 0, 2);
1554       return;
1555     }
1556
1557     if (appData.matchMode) {
1558         if(appData.tourneyFile[0]) { // start tourney from command line
1559             FILE *f;
1560             if(f = fopen(appData.tourneyFile, "r")) {
1561                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1562                 fclose(f);
1563                 appData.clockMode = TRUE;
1564                 SetGNUMode();
1565             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1566         }
1567         MatchEvent(TRUE);
1568     } else if (*appData.cmailGameName != NULLCHAR) {
1569         /* Set up cmail mode */
1570         ReloadCmailMsgEvent(TRUE);
1571     } else {
1572         /* Set up other modes */
1573         if (initialMode == AnalyzeFile) {
1574           if (*appData.loadGameFile == NULLCHAR) {
1575             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1576             return;
1577           }
1578         }
1579         if (*appData.loadGameFile != NULLCHAR) {
1580             (void) LoadGameFromFile(appData.loadGameFile,
1581                                     appData.loadGameIndex,
1582                                     appData.loadGameFile, TRUE);
1583         } else if (*appData.loadPositionFile != NULLCHAR) {
1584             (void) LoadPositionFromFile(appData.loadPositionFile,
1585                                         appData.loadPositionIndex,
1586                                         appData.loadPositionFile);
1587             /* [HGM] try to make self-starting even after FEN load */
1588             /* to allow automatic setup of fairy variants with wtm */
1589             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1590                 gameMode = BeginningOfGame;
1591                 setboardSpoiledMachineBlack = 1;
1592             }
1593             /* [HGM] loadPos: make that every new game uses the setup */
1594             /* from file as long as we do not switch variant          */
1595             if(!blackPlaysFirst) {
1596                 startedFromPositionFile = TRUE;
1597                 CopyBoard(filePosition, boards[0]);
1598             }
1599         }
1600         if (initialMode == AnalyzeMode) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1603             return;
1604           }
1605           if (appData.icsActive) {
1606             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1607             return;
1608           }
1609           AnalyzeModeEvent();
1610         } else if (initialMode == AnalyzeFile) {
1611           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1612           ShowThinkingEvent();
1613           AnalyzeFileEvent();
1614           AnalysisPeriodicEvent(1);
1615         } else if (initialMode == MachinePlaysWhite) {
1616           if (appData.noChessProgram) {
1617             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1618                               0, 2);
1619             return;
1620           }
1621           if (appData.icsActive) {
1622             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1623                               0, 2);
1624             return;
1625           }
1626           MachineWhiteEvent();
1627         } else if (initialMode == MachinePlaysBlack) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1630                               0, 2);
1631             return;
1632           }
1633           if (appData.icsActive) {
1634             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1635                               0, 2);
1636             return;
1637           }
1638           MachineBlackEvent();
1639         } else if (initialMode == TwoMachinesPlay) {
1640           if (appData.noChessProgram) {
1641             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1642                               0, 2);
1643             return;
1644           }
1645           if (appData.icsActive) {
1646             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1647                               0, 2);
1648             return;
1649           }
1650           TwoMachinesEvent();
1651         } else if (initialMode == EditGame) {
1652           EditGameEvent();
1653         } else if (initialMode == EditPosition) {
1654           EditPositionEvent();
1655         } else if (initialMode == Training) {
1656           if (*appData.loadGameFile == NULLCHAR) {
1657             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658             return;
1659           }
1660           TrainingEvent();
1661         }
1662     }
1663 }
1664
1665 void
1666 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1667 {
1668     MoveHistorySet( movelist, first, last, current, pvInfoList );
1669
1670     EvalGraphSet( first, last, current, pvInfoList );
1671
1672     MakeEngineOutputTitle();
1673 }
1674
1675 /*
1676  * Establish will establish a contact to a remote host.port.
1677  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1678  *  used to talk to the host.
1679  * Returns 0 if okay, error code if not.
1680  */
1681 int
1682 establish()
1683 {
1684     char buf[MSG_SIZ];
1685
1686     if (*appData.icsCommPort != NULLCHAR) {
1687         /* Talk to the host through a serial comm port */
1688         return OpenCommPort(appData.icsCommPort, &icsPR);
1689
1690     } else if (*appData.gateway != NULLCHAR) {
1691         if (*appData.remoteShell == NULLCHAR) {
1692             /* Use the rcmd protocol to run telnet program on a gateway host */
1693             snprintf(buf, sizeof(buf), "%s %s %s",
1694                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1695             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1696
1697         } else {
1698             /* Use the rsh program to run telnet program on a gateway host */
1699             if (*appData.remoteUser == NULLCHAR) {
1700                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1701                         appData.gateway, appData.telnetProgram,
1702                         appData.icsHost, appData.icsPort);
1703             } else {
1704                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1705                         appData.remoteShell, appData.gateway,
1706                         appData.remoteUser, appData.telnetProgram,
1707                         appData.icsHost, appData.icsPort);
1708             }
1709             return StartChildProcess(buf, "", &icsPR);
1710
1711         }
1712     } else if (appData.useTelnet) {
1713         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1714
1715     } else {
1716         /* TCP socket interface differs somewhat between
1717            Unix and NT; handle details in the front end.
1718            */
1719         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1720     }
1721 }
1722
1723 void EscapeExpand(char *p, char *q)
1724 {       // [HGM] initstring: routine to shape up string arguments
1725         while(*p++ = *q++) if(p[-1] == '\\')
1726             switch(*q++) {
1727                 case 'n': p[-1] = '\n'; break;
1728                 case 'r': p[-1] = '\r'; break;
1729                 case 't': p[-1] = '\t'; break;
1730                 case '\\': p[-1] = '\\'; break;
1731                 case 0: *p = 0; return;
1732                 default: p[-1] = q[-1]; break;
1733             }
1734 }
1735
1736 void
1737 show_bytes(fp, buf, count)
1738      FILE *fp;
1739      char *buf;
1740      int count;
1741 {
1742     while (count--) {
1743         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1744             fprintf(fp, "\\%03o", *buf & 0xff);
1745         } else {
1746             putc(*buf, fp);
1747         }
1748         buf++;
1749     }
1750     fflush(fp);
1751 }
1752
1753 /* Returns an errno value */
1754 int
1755 OutputMaybeTelnet(pr, message, count, outError)
1756      ProcRef pr;
1757      char *message;
1758      int count;
1759      int *outError;
1760 {
1761     char buf[8192], *p, *q, *buflim;
1762     int left, newcount, outcount;
1763
1764     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1765         *appData.gateway != NULLCHAR) {
1766         if (appData.debugMode) {
1767             fprintf(debugFP, ">ICS: ");
1768             show_bytes(debugFP, message, count);
1769             fprintf(debugFP, "\n");
1770         }
1771         return OutputToProcess(pr, message, count, outError);
1772     }
1773
1774     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1775     p = message;
1776     q = buf;
1777     left = count;
1778     newcount = 0;
1779     while (left) {
1780         if (q >= buflim) {
1781             if (appData.debugMode) {
1782                 fprintf(debugFP, ">ICS: ");
1783                 show_bytes(debugFP, buf, newcount);
1784                 fprintf(debugFP, "\n");
1785             }
1786             outcount = OutputToProcess(pr, buf, newcount, outError);
1787             if (outcount < newcount) return -1; /* to be sure */
1788             q = buf;
1789             newcount = 0;
1790         }
1791         if (*p == '\n') {
1792             *q++ = '\r';
1793             newcount++;
1794         } else if (((unsigned char) *p) == TN_IAC) {
1795             *q++ = (char) TN_IAC;
1796             newcount ++;
1797         }
1798         *q++ = *p++;
1799         newcount++;
1800         left--;
1801     }
1802     if (appData.debugMode) {
1803         fprintf(debugFP, ">ICS: ");
1804         show_bytes(debugFP, buf, newcount);
1805         fprintf(debugFP, "\n");
1806     }
1807     outcount = OutputToProcess(pr, buf, newcount, outError);
1808     if (outcount < newcount) return -1; /* to be sure */
1809     return count;
1810 }
1811
1812 void
1813 read_from_player(isr, closure, message, count, error)
1814      InputSourceRef isr;
1815      VOIDSTAR closure;
1816      char *message;
1817      int count;
1818      int error;
1819 {
1820     int outError, outCount;
1821     static int gotEof = 0;
1822
1823     /* Pass data read from player on to ICS */
1824     if (count > 0) {
1825         gotEof = 0;
1826         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1827         if (outCount < count) {
1828             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1829         }
1830     } else if (count < 0) {
1831         RemoveInputSource(isr);
1832         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1833     } else if (gotEof++ > 0) {
1834         RemoveInputSource(isr);
1835         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1836     }
1837 }
1838
1839 void
1840 KeepAlive()
1841 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1842     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1843     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1844     SendToICS("date\n");
1845     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1846 }
1847
1848 /* added routine for printf style output to ics */
1849 void ics_printf(char *format, ...)
1850 {
1851     char buffer[MSG_SIZ];
1852     va_list args;
1853
1854     va_start(args, format);
1855     vsnprintf(buffer, sizeof(buffer), format, args);
1856     buffer[sizeof(buffer)-1] = '\0';
1857     SendToICS(buffer);
1858     va_end(args);
1859 }
1860
1861 void
1862 SendToICS(s)
1863      char *s;
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NULL) return;
1868
1869     count = strlen(s);
1870     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1871     if (outCount < count) {
1872         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1873     }
1874 }
1875
1876 /* This is used for sending logon scripts to the ICS. Sending
1877    without a delay causes problems when using timestamp on ICC
1878    (at least on my machine). */
1879 void
1880 SendToICSDelayed(s,msdelay)
1881      char *s;
1882      long msdelay;
1883 {
1884     int count, outCount, outError;
1885
1886     if (icsPR == NULL) return;
1887
1888     count = strlen(s);
1889     if (appData.debugMode) {
1890         fprintf(debugFP, ">ICS: ");
1891         show_bytes(debugFP, s, count);
1892         fprintf(debugFP, "\n");
1893     }
1894     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1895                                       msdelay);
1896     if (outCount < count) {
1897         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898     }
1899 }
1900
1901
1902 /* Remove all highlighting escape sequences in s
1903    Also deletes any suffix starting with '('
1904    */
1905 char *
1906 StripHighlightAndTitle(s)
1907      char *s;
1908 {
1909     static char retbuf[MSG_SIZ];
1910     char *p = retbuf;
1911
1912     while (*s != NULLCHAR) {
1913         while (*s == '\033') {
1914             while (*s != NULLCHAR && !isalpha(*s)) s++;
1915             if (*s != NULLCHAR) s++;
1916         }
1917         while (*s != NULLCHAR && *s != '\033') {
1918             if (*s == '(' || *s == '[') {
1919                 *p = NULLCHAR;
1920                 return retbuf;
1921             }
1922             *p++ = *s++;
1923         }
1924     }
1925     *p = NULLCHAR;
1926     return retbuf;
1927 }
1928
1929 /* Remove all highlighting escape sequences in s */
1930 char *
1931 StripHighlight(s)
1932      char *s;
1933 {
1934     static char retbuf[MSG_SIZ];
1935     char *p = retbuf;
1936
1937     while (*s != NULLCHAR) {
1938         while (*s == '\033') {
1939             while (*s != NULLCHAR && !isalpha(*s)) s++;
1940             if (*s != NULLCHAR) s++;
1941         }
1942         while (*s != NULLCHAR && *s != '\033') {
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 char *variantNames[] = VARIANT_NAMES;
1951 char *
1952 VariantName(v)
1953      VariantClass v;
1954 {
1955     return variantNames[v];
1956 }
1957
1958
1959 /* Identify a variant from the strings the chess servers use or the
1960    PGN Variant tag names we use. */
1961 VariantClass
1962 StringToVariant(e)
1963      char *e;
1964 {
1965     char *p;
1966     int wnum = -1;
1967     VariantClass v = VariantNormal;
1968     int i, found = FALSE;
1969     char buf[MSG_SIZ];
1970     int len;
1971
1972     if (!e) return v;
1973
1974     /* [HGM] skip over optional board-size prefixes */
1975     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1976         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1977         while( *e++ != '_');
1978     }
1979
1980     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1981         v = VariantNormal;
1982         found = TRUE;
1983     } else
1984     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1985       if (StrCaseStr(e, variantNames[i])) {
1986         v = (VariantClass) i;
1987         found = TRUE;
1988         break;
1989       }
1990     }
1991
1992     if (!found) {
1993       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1994           || StrCaseStr(e, "wild/fr")
1995           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1996         v = VariantFischeRandom;
1997       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1998                  (i = 1, p = StrCaseStr(e, "w"))) {
1999         p += i;
2000         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2001         if (isdigit(*p)) {
2002           wnum = atoi(p);
2003         } else {
2004           wnum = -1;
2005         }
2006         switch (wnum) {
2007         case 0: /* FICS only, actually */
2008         case 1:
2009           /* Castling legal even if K starts on d-file */
2010           v = VariantWildCastle;
2011           break;
2012         case 2:
2013         case 3:
2014         case 4:
2015           /* Castling illegal even if K & R happen to start in
2016              normal positions. */
2017           v = VariantNoCastle;
2018           break;
2019         case 5:
2020         case 7:
2021         case 8:
2022         case 10:
2023         case 11:
2024         case 12:
2025         case 13:
2026         case 14:
2027         case 15:
2028         case 18:
2029         case 19:
2030           /* Castling legal iff K & R start in normal positions */
2031           v = VariantNormal;
2032           break;
2033         case 6:
2034         case 20:
2035         case 21:
2036           /* Special wilds for position setup; unclear what to do here */
2037           v = VariantLoadable;
2038           break;
2039         case 9:
2040           /* Bizarre ICC game */
2041           v = VariantTwoKings;
2042           break;
2043         case 16:
2044           v = VariantKriegspiel;
2045           break;
2046         case 17:
2047           v = VariantLosers;
2048           break;
2049         case 22:
2050           v = VariantFischeRandom;
2051           break;
2052         case 23:
2053           v = VariantCrazyhouse;
2054           break;
2055         case 24:
2056           v = VariantBughouse;
2057           break;
2058         case 25:
2059           v = Variant3Check;
2060           break;
2061         case 26:
2062           /* Not quite the same as FICS suicide! */
2063           v = VariantGiveaway;
2064           break;
2065         case 27:
2066           v = VariantAtomic;
2067           break;
2068         case 28:
2069           v = VariantShatranj;
2070           break;
2071
2072         /* Temporary names for future ICC types.  The name *will* change in
2073            the next xboard/WinBoard release after ICC defines it. */
2074         case 29:
2075           v = Variant29;
2076           break;
2077         case 30:
2078           v = Variant30;
2079           break;
2080         case 31:
2081           v = Variant31;
2082           break;
2083         case 32:
2084           v = Variant32;
2085           break;
2086         case 33:
2087           v = Variant33;
2088           break;
2089         case 34:
2090           v = Variant34;
2091           break;
2092         case 35:
2093           v = Variant35;
2094           break;
2095         case 36:
2096           v = Variant36;
2097           break;
2098         case 37:
2099           v = VariantShogi;
2100           break;
2101         case 38:
2102           v = VariantXiangqi;
2103           break;
2104         case 39:
2105           v = VariantCourier;
2106           break;
2107         case 40:
2108           v = VariantGothic;
2109           break;
2110         case 41:
2111           v = VariantCapablanca;
2112           break;
2113         case 42:
2114           v = VariantKnightmate;
2115           break;
2116         case 43:
2117           v = VariantFairy;
2118           break;
2119         case 44:
2120           v = VariantCylinder;
2121           break;
2122         case 45:
2123           v = VariantFalcon;
2124           break;
2125         case 46:
2126           v = VariantCapaRandom;
2127           break;
2128         case 47:
2129           v = VariantBerolina;
2130           break;
2131         case 48:
2132           v = VariantJanus;
2133           break;
2134         case 49:
2135           v = VariantSuper;
2136           break;
2137         case 50:
2138           v = VariantGreat;
2139           break;
2140         case -1:
2141           /* Found "wild" or "w" in the string but no number;
2142              must assume it's normal chess. */
2143           v = VariantNormal;
2144           break;
2145         default:
2146           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2147           if( (len > MSG_SIZ) && appData.debugMode )
2148             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2149
2150           DisplayError(buf, 0);
2151           v = VariantUnknown;
2152           break;
2153         }
2154       }
2155     }
2156     if (appData.debugMode) {
2157       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2158               e, wnum, VariantName(v));
2159     }
2160     return v;
2161 }
2162
2163 static int leftover_start = 0, leftover_len = 0;
2164 char star_match[STAR_MATCH_N][MSG_SIZ];
2165
2166 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2167    advance *index beyond it, and set leftover_start to the new value of
2168    *index; else return FALSE.  If pattern contains the character '*', it
2169    matches any sequence of characters not containing '\r', '\n', or the
2170    character following the '*' (if any), and the matched sequence(s) are
2171    copied into star_match.
2172    */
2173 int
2174 looking_at(buf, index, pattern)
2175      char *buf;
2176      int *index;
2177      char *pattern;
2178 {
2179     char *bufp = &buf[*index], *patternp = pattern;
2180     int star_count = 0;
2181     char *matchp = star_match[0];
2182
2183     for (;;) {
2184         if (*patternp == NULLCHAR) {
2185             *index = leftover_start = bufp - buf;
2186             *matchp = NULLCHAR;
2187             return TRUE;
2188         }
2189         if (*bufp == NULLCHAR) return FALSE;
2190         if (*patternp == '*') {
2191             if (*bufp == *(patternp + 1)) {
2192                 *matchp = NULLCHAR;
2193                 matchp = star_match[++star_count];
2194                 patternp += 2;
2195                 bufp++;
2196                 continue;
2197             } else if (*bufp == '\n' || *bufp == '\r') {
2198                 patternp++;
2199                 if (*patternp == NULLCHAR)
2200                   continue;
2201                 else
2202                   return FALSE;
2203             } else {
2204                 *matchp++ = *bufp++;
2205                 continue;
2206             }
2207         }
2208         if (*patternp != *bufp) return FALSE;
2209         patternp++;
2210         bufp++;
2211     }
2212 }
2213
2214 void
2215 SendToPlayer(data, length)
2216      char *data;
2217      int length;
2218 {
2219     int error, outCount;
2220     outCount = OutputToProcess(NoProc, data, length, &error);
2221     if (outCount < length) {
2222         DisplayFatalError(_("Error writing to display"), error, 1);
2223     }
2224 }
2225
2226 void
2227 PackHolding(packed, holding)
2228      char packed[];
2229      char *holding;
2230 {
2231     char *p = holding;
2232     char *q = packed;
2233     int runlength = 0;
2234     int curr = 9999;
2235     do {
2236         if (*p == curr) {
2237             runlength++;
2238         } else {
2239             switch (runlength) {
2240               case 0:
2241                 break;
2242               case 1:
2243                 *q++ = curr;
2244                 break;
2245               case 2:
2246                 *q++ = curr;
2247                 *q++ = curr;
2248                 break;
2249               default:
2250                 sprintf(q, "%d", runlength);
2251                 while (*q) q++;
2252                 *q++ = curr;
2253                 break;
2254             }
2255             runlength = 1;
2256             curr = *p;
2257         }
2258     } while (*p++);
2259     *q = NULLCHAR;
2260 }
2261
2262 /* Telnet protocol requests from the front end */
2263 void
2264 TelnetRequest(ddww, option)
2265      unsigned char ddww, option;
2266 {
2267     unsigned char msg[3];
2268     int outCount, outError;
2269
2270     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2271
2272     if (appData.debugMode) {
2273         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2274         switch (ddww) {
2275           case TN_DO:
2276             ddwwStr = "DO";
2277             break;
2278           case TN_DONT:
2279             ddwwStr = "DONT";
2280             break;
2281           case TN_WILL:
2282             ddwwStr = "WILL";
2283             break;
2284           case TN_WONT:
2285             ddwwStr = "WONT";
2286             break;
2287           default:
2288             ddwwStr = buf1;
2289             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2290             break;
2291         }
2292         switch (option) {
2293           case TN_ECHO:
2294             optionStr = "ECHO";
2295             break;
2296           default:
2297             optionStr = buf2;
2298             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2299             break;
2300         }
2301         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2302     }
2303     msg[0] = TN_IAC;
2304     msg[1] = ddww;
2305     msg[2] = option;
2306     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2307     if (outCount < 3) {
2308         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2309     }
2310 }
2311
2312 void
2313 DoEcho()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DO, TN_ECHO);
2317 }
2318
2319 void
2320 DontEcho()
2321 {
2322     if (!appData.icsActive) return;
2323     TelnetRequest(TN_DONT, TN_ECHO);
2324 }
2325
2326 void
2327 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2328 {
2329     /* put the holdings sent to us by the server on the board holdings area */
2330     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2331     char p;
2332     ChessSquare piece;
2333
2334     if(gameInfo.holdingsWidth < 2)  return;
2335     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2336         return; // prevent overwriting by pre-board holdings
2337
2338     if( (int)lowestPiece >= BlackPawn ) {
2339         holdingsColumn = 0;
2340         countsColumn = 1;
2341         holdingsStartRow = BOARD_HEIGHT-1;
2342         direction = -1;
2343     } else {
2344         holdingsColumn = BOARD_WIDTH-1;
2345         countsColumn = BOARD_WIDTH-2;
2346         holdingsStartRow = 0;
2347         direction = 1;
2348     }
2349
2350     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2351         board[i][holdingsColumn] = EmptySquare;
2352         board[i][countsColumn]   = (ChessSquare) 0;
2353     }
2354     while( (p=*holdings++) != NULLCHAR ) {
2355         piece = CharToPiece( ToUpper(p) );
2356         if(piece == EmptySquare) continue;
2357         /*j = (int) piece - (int) WhitePawn;*/
2358         j = PieceToNumber(piece);
2359         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2360         if(j < 0) continue;               /* should not happen */
2361         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2362         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2363         board[holdingsStartRow+j*direction][countsColumn]++;
2364     }
2365 }
2366
2367
2368 void
2369 VariantSwitch(Board board, VariantClass newVariant)
2370 {
2371    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2372    static Board oldBoard;
2373
2374    startedFromPositionFile = FALSE;
2375    if(gameInfo.variant == newVariant) return;
2376
2377    /* [HGM] This routine is called each time an assignment is made to
2378     * gameInfo.variant during a game, to make sure the board sizes
2379     * are set to match the new variant. If that means adding or deleting
2380     * holdings, we shift the playing board accordingly
2381     * This kludge is needed because in ICS observe mode, we get boards
2382     * of an ongoing game without knowing the variant, and learn about the
2383     * latter only later. This can be because of the move list we requested,
2384     * in which case the game history is refilled from the beginning anyway,
2385     * but also when receiving holdings of a crazyhouse game. In the latter
2386     * case we want to add those holdings to the already received position.
2387     */
2388
2389
2390    if (appData.debugMode) {
2391      fprintf(debugFP, "Switch board from %s to %s\n",
2392              VariantName(gameInfo.variant), VariantName(newVariant));
2393      setbuf(debugFP, NULL);
2394    }
2395    shuffleOpenings = 0;       /* [HGM] shuffle */
2396    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2397    switch(newVariant)
2398      {
2399      case VariantShogi:
2400        newWidth = 9;  newHeight = 9;
2401        gameInfo.holdingsSize = 7;
2402      case VariantBughouse:
2403      case VariantCrazyhouse:
2404        newHoldingsWidth = 2; break;
2405      case VariantGreat:
2406        newWidth = 10;
2407      case VariantSuper:
2408        newHoldingsWidth = 2;
2409        gameInfo.holdingsSize = 8;
2410        break;
2411      case VariantGothic:
2412      case VariantCapablanca:
2413      case VariantCapaRandom:
2414        newWidth = 10;
2415      default:
2416        newHoldingsWidth = gameInfo.holdingsSize = 0;
2417      };
2418
2419    if(newWidth  != gameInfo.boardWidth  ||
2420       newHeight != gameInfo.boardHeight ||
2421       newHoldingsWidth != gameInfo.holdingsWidth ) {
2422
2423      /* shift position to new playing area, if needed */
2424      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429        for(i=0; i<newHeight; i++) {
2430          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2431          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2432        }
2433      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438      }
2439      gameInfo.boardWidth  = newWidth;
2440      gameInfo.boardHeight = newHeight;
2441      gameInfo.holdingsWidth = newHoldingsWidth;
2442      gameInfo.variant = newVariant;
2443      InitDrawingSizes(-2, 0);
2444    } else gameInfo.variant = newVariant;
2445    CopyBoard(oldBoard, board);   // remember correctly formatted board
2446      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2447    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2448 }
2449
2450 static int loggedOn = FALSE;
2451
2452 /*-- Game start info cache: --*/
2453 int gs_gamenum;
2454 char gs_kind[MSG_SIZ];
2455 static char player1Name[128] = "";
2456 static char player2Name[128] = "";
2457 static char cont_seq[] = "\n\\   ";
2458 static int player1Rating = -1;
2459 static int player2Rating = -1;
2460 /*----------------------------*/
2461
2462 ColorClass curColor = ColorNormal;
2463 int suppressKibitz = 0;
2464
2465 // [HGM] seekgraph
2466 Boolean soughtPending = FALSE;
2467 Boolean seekGraphUp;
2468 #define MAX_SEEK_ADS 200
2469 #define SQUARE 0x80
2470 char *seekAdList[MAX_SEEK_ADS];
2471 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2472 float tcList[MAX_SEEK_ADS];
2473 char colorList[MAX_SEEK_ADS];
2474 int nrOfSeekAds = 0;
2475 int minRating = 1010, maxRating = 2800;
2476 int hMargin = 10, vMargin = 20, h, w;
2477 extern int squareSize, lineGap;
2478
2479 void
2480 PlotSeekAd(int i)
2481 {
2482         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2483         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2484         if(r < minRating+100 && r >=0 ) r = minRating+100;
2485         if(r > maxRating) r = maxRating;
2486         if(tc < 1.) tc = 1.;
2487         if(tc > 95.) tc = 95.;
2488         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2489         y = ((double)r - minRating)/(maxRating - minRating)
2490             * (h-vMargin-squareSize/8-1) + vMargin;
2491         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2492         if(strstr(seekAdList[i], " u ")) color = 1;
2493         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2494            !strstr(seekAdList[i], "bullet") &&
2495            !strstr(seekAdList[i], "blitz") &&
2496            !strstr(seekAdList[i], "standard") ) color = 2;
2497         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2498         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2499 }
2500
2501 void
2502 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2503 {
2504         char buf[MSG_SIZ], *ext = "";
2505         VariantClass v = StringToVariant(type);
2506         if(strstr(type, "wild")) {
2507             ext = type + 4; // append wild number
2508             if(v == VariantFischeRandom) type = "chess960"; else
2509             if(v == VariantLoadable) type = "setup"; else
2510             type = VariantName(v);
2511         }
2512         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2513         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2514             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2515             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2516             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2517             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2518             seekNrList[nrOfSeekAds] = nr;
2519             zList[nrOfSeekAds] = 0;
2520             seekAdList[nrOfSeekAds++] = StrSave(buf);
2521             if(plot) PlotSeekAd(nrOfSeekAds-1);
2522         }
2523 }
2524
2525 void
2526 EraseSeekDot(int i)
2527 {
2528     int x = xList[i], y = yList[i], d=squareSize/4, k;
2529     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2530     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2531     // now replot every dot that overlapped
2532     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2533         int xx = xList[k], yy = yList[k];
2534         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2535             DrawSeekDot(xx, yy, colorList[k]);
2536     }
2537 }
2538
2539 void
2540 RemoveSeekAd(int nr)
2541 {
2542         int i;
2543         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2544             EraseSeekDot(i);
2545             if(seekAdList[i]) free(seekAdList[i]);
2546             seekAdList[i] = seekAdList[--nrOfSeekAds];
2547             seekNrList[i] = seekNrList[nrOfSeekAds];
2548             ratingList[i] = ratingList[nrOfSeekAds];
2549             colorList[i]  = colorList[nrOfSeekAds];
2550             tcList[i] = tcList[nrOfSeekAds];
2551             xList[i]  = xList[nrOfSeekAds];
2552             yList[i]  = yList[nrOfSeekAds];
2553             zList[i]  = zList[nrOfSeekAds];
2554             seekAdList[nrOfSeekAds] = NULL;
2555             break;
2556         }
2557 }
2558
2559 Boolean
2560 MatchSoughtLine(char *line)
2561 {
2562     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2563     int nr, base, inc, u=0; char dummy;
2564
2565     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2566        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2567        (u=1) &&
2568        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2570         // match: compact and save the line
2571         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2572         return TRUE;
2573     }
2574     return FALSE;
2575 }
2576
2577 int
2578 DrawSeekGraph()
2579 {
2580     int i;
2581     if(!seekGraphUp) return FALSE;
2582     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2583     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2584
2585     DrawSeekBackground(0, 0, w, h);
2586     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2587     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2588     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2589         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2590         yy = h-1-yy;
2591         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2592         if(i%500 == 0) {
2593             char buf[MSG_SIZ];
2594             snprintf(buf, MSG_SIZ, "%d", i);
2595             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2596         }
2597     }
2598     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2599     for(i=1; i<100; i+=(i<10?1:5)) {
2600         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2601         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2602         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2606         }
2607     }
2608     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2609     return TRUE;
2610 }
2611
2612 int SeekGraphClick(ClickType click, int x, int y, int moving)
2613 {
2614     static int lastDown = 0, displayed = 0, lastSecond;
2615     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2616         if(click == Release || moving) return FALSE;
2617         nrOfSeekAds = 0;
2618         soughtPending = TRUE;
2619         SendToICS(ics_prefix);
2620         SendToICS("sought\n"); // should this be "sought all"?
2621     } else { // issue challenge based on clicked ad
2622         int dist = 10000; int i, closest = 0, second = 0;
2623         for(i=0; i<nrOfSeekAds; i++) {
2624             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2625             if(d < dist) { dist = d; closest = i; }
2626             second += (d - zList[i] < 120); // count in-range ads
2627             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2628         }
2629         if(dist < 120) {
2630             char buf[MSG_SIZ];
2631             second = (second > 1);
2632             if(displayed != closest || second != lastSecond) {
2633                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2634                 lastSecond = second; displayed = closest;
2635             }
2636             if(click == Press) {
2637                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2638                 lastDown = closest;
2639                 return TRUE;
2640             } // on press 'hit', only show info
2641             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2642             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2643             SendToICS(ics_prefix);
2644             SendToICS(buf);
2645             return TRUE; // let incoming board of started game pop down the graph
2646         } else if(click == Release) { // release 'miss' is ignored
2647             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2648             if(moving == 2) { // right up-click
2649                 nrOfSeekAds = 0; // refresh graph
2650                 soughtPending = TRUE;
2651                 SendToICS(ics_prefix);
2652                 SendToICS("sought\n"); // should this be "sought all"?
2653             }
2654             return TRUE;
2655         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2656         // press miss or release hit 'pop down' seek graph
2657         seekGraphUp = FALSE;
2658         DrawPosition(TRUE, NULL);
2659     }
2660     return TRUE;
2661 }
2662
2663 void
2664 read_from_ics(isr, closure, data, count, error)
2665      InputSourceRef isr;
2666      VOIDSTAR closure;
2667      char *data;
2668      int count;
2669      int error;
2670 {
2671 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2672 #define STARTED_NONE 0
2673 #define STARTED_MOVES 1
2674 #define STARTED_BOARD 2
2675 #define STARTED_OBSERVE 3
2676 #define STARTED_HOLDINGS 4
2677 #define STARTED_CHATTER 5
2678 #define STARTED_COMMENT 6
2679 #define STARTED_MOVES_NOHIDE 7
2680
2681     static int started = STARTED_NONE;
2682     static char parse[20000];
2683     static int parse_pos = 0;
2684     static char buf[BUF_SIZE + 1];
2685     static int firstTime = TRUE, intfSet = FALSE;
2686     static ColorClass prevColor = ColorNormal;
2687     static int savingComment = FALSE;
2688     static int cmatch = 0; // continuation sequence match
2689     char *bp;
2690     char str[MSG_SIZ];
2691     int i, oldi;
2692     int buf_len;
2693     int next_out;
2694     int tkind;
2695     int backup;    /* [DM] For zippy color lines */
2696     char *p;
2697     char talker[MSG_SIZ]; // [HGM] chat
2698     int channel;
2699
2700     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2701
2702     if (appData.debugMode) {
2703       if (!error) {
2704         fprintf(debugFP, "<ICS: ");
2705         show_bytes(debugFP, data, count);
2706         fprintf(debugFP, "\n");
2707       }
2708     }
2709
2710     if (appData.debugMode) { int f = forwardMostMove;
2711         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2712                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2713                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2714     }
2715     if (count > 0) {
2716         /* If last read ended with a partial line that we couldn't parse,
2717            prepend it to the new read and try again. */
2718         if (leftover_len > 0) {
2719             for (i=0; i<leftover_len; i++)
2720               buf[i] = buf[leftover_start + i];
2721         }
2722
2723     /* copy new characters into the buffer */
2724     bp = buf + leftover_len;
2725     buf_len=leftover_len;
2726     for (i=0; i<count; i++)
2727     {
2728         // ignore these
2729         if (data[i] == '\r')
2730             continue;
2731
2732         // join lines split by ICS?
2733         if (!appData.noJoin)
2734         {
2735             /*
2736                 Joining just consists of finding matches against the
2737                 continuation sequence, and discarding that sequence
2738                 if found instead of copying it.  So, until a match
2739                 fails, there's nothing to do since it might be the
2740                 complete sequence, and thus, something we don't want
2741                 copied.
2742             */
2743             if (data[i] == cont_seq[cmatch])
2744             {
2745                 cmatch++;
2746                 if (cmatch == strlen(cont_seq))
2747                 {
2748                     cmatch = 0; // complete match.  just reset the counter
2749
2750                     /*
2751                         it's possible for the ICS to not include the space
2752                         at the end of the last word, making our [correct]
2753                         join operation fuse two separate words.  the server
2754                         does this when the space occurs at the width setting.
2755                     */
2756                     if (!buf_len || buf[buf_len-1] != ' ')
2757                     {
2758                         *bp++ = ' ';
2759                         buf_len++;
2760                     }
2761                 }
2762                 continue;
2763             }
2764             else if (cmatch)
2765             {
2766                 /*
2767                     match failed, so we have to copy what matched before
2768                     falling through and copying this character.  In reality,
2769                     this will only ever be just the newline character, but
2770                     it doesn't hurt to be precise.
2771                 */
2772                 strncpy(bp, cont_seq, cmatch);
2773                 bp += cmatch;
2774                 buf_len += cmatch;
2775                 cmatch = 0;
2776             }
2777         }
2778
2779         // copy this char
2780         *bp++ = data[i];
2781         buf_len++;
2782     }
2783
2784         buf[buf_len] = NULLCHAR;
2785 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2786         next_out = 0;
2787         leftover_start = 0;
2788
2789         i = 0;
2790         while (i < buf_len) {
2791             /* Deal with part of the TELNET option negotiation
2792                protocol.  We refuse to do anything beyond the
2793                defaults, except that we allow the WILL ECHO option,
2794                which ICS uses to turn off password echoing when we are
2795                directly connected to it.  We reject this option
2796                if localLineEditing mode is on (always on in xboard)
2797                and we are talking to port 23, which might be a real
2798                telnet server that will try to keep WILL ECHO on permanently.
2799              */
2800             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2801                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2802                 unsigned char option;
2803                 oldi = i;
2804                 switch ((unsigned char) buf[++i]) {
2805                   case TN_WILL:
2806                     if (appData.debugMode)
2807                       fprintf(debugFP, "\n<WILL ");
2808                     switch (option = (unsigned char) buf[++i]) {
2809                       case TN_ECHO:
2810                         if (appData.debugMode)
2811                           fprintf(debugFP, "ECHO ");
2812                         /* Reply only if this is a change, according
2813                            to the protocol rules. */
2814                         if (remoteEchoOption) break;
2815                         if (appData.localLineEditing &&
2816                             atoi(appData.icsPort) == TN_PORT) {
2817                             TelnetRequest(TN_DONT, TN_ECHO);
2818                         } else {
2819                             EchoOff();
2820                             TelnetRequest(TN_DO, TN_ECHO);
2821                             remoteEchoOption = TRUE;
2822                         }
2823                         break;
2824                       default:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "%d ", option);
2827                         /* Whatever this is, we don't want it. */
2828                         TelnetRequest(TN_DONT, option);
2829                         break;
2830                     }
2831                     break;
2832                   case TN_WONT:
2833                     if (appData.debugMode)
2834                       fprintf(debugFP, "\n<WONT ");
2835                     switch (option = (unsigned char) buf[++i]) {
2836                       case TN_ECHO:
2837                         if (appData.debugMode)
2838                           fprintf(debugFP, "ECHO ");
2839                         /* Reply only if this is a change, according
2840                            to the protocol rules. */
2841                         if (!remoteEchoOption) break;
2842                         EchoOn();
2843                         TelnetRequest(TN_DONT, TN_ECHO);
2844                         remoteEchoOption = FALSE;
2845                         break;
2846                       default:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "%d ", (unsigned char) option);
2849                         /* Whatever this is, it must already be turned
2850                            off, because we never agree to turn on
2851                            anything non-default, so according to the
2852                            protocol rules, we don't reply. */
2853                         break;
2854                     }
2855                     break;
2856                   case TN_DO:
2857                     if (appData.debugMode)
2858                       fprintf(debugFP, "\n<DO ");
2859                     switch (option = (unsigned char) buf[++i]) {
2860                       default:
2861                         /* Whatever this is, we refuse to do it. */
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", option);
2864                         TelnetRequest(TN_WONT, option);
2865                         break;
2866                     }
2867                     break;
2868                   case TN_DONT:
2869                     if (appData.debugMode)
2870                       fprintf(debugFP, "\n<DONT ");
2871                     switch (option = (unsigned char) buf[++i]) {
2872                       default:
2873                         if (appData.debugMode)
2874                           fprintf(debugFP, "%d ", option);
2875                         /* Whatever this is, we are already not doing
2876                            it, because we never agree to do anything
2877                            non-default, so according to the protocol
2878                            rules, we don't reply. */
2879                         break;
2880                     }
2881                     break;
2882                   case TN_IAC:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<IAC ");
2885                     /* Doubled IAC; pass it through */
2886                     i--;
2887                     break;
2888                   default:
2889                     if (appData.debugMode)
2890                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2891                     /* Drop all other telnet commands on the floor */
2892                     break;
2893                 }
2894                 if (oldi > next_out)
2895                   SendToPlayer(&buf[next_out], oldi - next_out);
2896                 if (++i > next_out)
2897                   next_out = i;
2898                 continue;
2899             }
2900
2901             /* OK, this at least will *usually* work */
2902             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2903                 loggedOn = TRUE;
2904             }
2905
2906             if (loggedOn && !intfSet) {
2907                 if (ics_type == ICS_ICC) {
2908                   snprintf(str, MSG_SIZ,
2909                           "/set-quietly interface %s\n/set-quietly style 12\n",
2910                           programVersion);
2911                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2912                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2913                 } else if (ics_type == ICS_CHESSNET) {
2914                   snprintf(str, MSG_SIZ, "/style 12\n");
2915                 } else {
2916                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2917                   strcat(str, programVersion);
2918                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2919                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2920                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2921 #ifdef WIN32
2922                   strcat(str, "$iset nohighlight 1\n");
2923 #endif
2924                   strcat(str, "$iset lock 1\n$style 12\n");
2925                 }
2926                 SendToICS(str);
2927                 NotifyFrontendLogin();
2928                 intfSet = TRUE;
2929             }
2930
2931             if (started == STARTED_COMMENT) {
2932                 /* Accumulate characters in comment */
2933                 parse[parse_pos++] = buf[i];
2934                 if (buf[i] == '\n') {
2935                     parse[parse_pos] = NULLCHAR;
2936                     if(chattingPartner>=0) {
2937                         char mess[MSG_SIZ];
2938                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2939                         OutputChatMessage(chattingPartner, mess);
2940                         chattingPartner = -1;
2941                         next_out = i+1; // [HGM] suppress printing in ICS window
2942                     } else
2943                     if(!suppressKibitz) // [HGM] kibitz
2944                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2945                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2946                         int nrDigit = 0, nrAlph = 0, j;
2947                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2948                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2949                         parse[parse_pos] = NULLCHAR;
2950                         // try to be smart: if it does not look like search info, it should go to
2951                         // ICS interaction window after all, not to engine-output window.
2952                         for(j=0; j<parse_pos; j++) { // count letters and digits
2953                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2954                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2955                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2956                         }
2957                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2958                             int depth=0; float score;
2959                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2960                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2961                                 pvInfoList[forwardMostMove-1].depth = depth;
2962                                 pvInfoList[forwardMostMove-1].score = 100*score;
2963                             }
2964                             OutputKibitz(suppressKibitz, parse);
2965                         } else {
2966                             char tmp[MSG_SIZ];
2967                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2968                             SendToPlayer(tmp, strlen(tmp));
2969                         }
2970                         next_out = i+1; // [HGM] suppress printing in ICS window
2971                     }
2972                     started = STARTED_NONE;
2973                 } else {
2974                     /* Don't match patterns against characters in comment */
2975                     i++;
2976                     continue;
2977                 }
2978             }
2979             if (started == STARTED_CHATTER) {
2980                 if (buf[i] != '\n') {
2981                     /* Don't match patterns against characters in chatter */
2982                     i++;
2983                     continue;
2984                 }
2985                 started = STARTED_NONE;
2986                 if(suppressKibitz) next_out = i+1;
2987             }
2988
2989             /* Kludge to deal with rcmd protocol */
2990             if (firstTime && looking_at(buf, &i, "\001*")) {
2991                 DisplayFatalError(&buf[1], 0, 1);
2992                 continue;
2993             } else {
2994                 firstTime = FALSE;
2995             }
2996
2997             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2998                 ics_type = ICS_ICC;
2999                 ics_prefix = "/";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3005                 ics_type = ICS_FICS;
3006                 ics_prefix = "$";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3012                 ics_type = ICS_CHESSNET;
3013                 ics_prefix = "/";
3014                 if (appData.debugMode)
3015                   fprintf(debugFP, "ics_type %d\n", ics_type);
3016                 continue;
3017             }
3018
3019             if (!loggedOn &&
3020                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3021                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3022                  looking_at(buf, &i, "will be \"*\""))) {
3023               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3024               continue;
3025             }
3026
3027             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3028               char buf[MSG_SIZ];
3029               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3030               DisplayIcsInteractionTitle(buf);
3031               have_set_title = TRUE;
3032             }
3033
3034             /* skip finger notes */
3035             if (started == STARTED_NONE &&
3036                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3037                  (buf[i] == '1' && buf[i+1] == '0')) &&
3038                 buf[i+2] == ':' && buf[i+3] == ' ') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             oldi = i;
3045             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3046             if(appData.seekGraph) {
3047                 if(soughtPending && MatchSoughtLine(buf+i)) {
3048                     i = strstr(buf+i, "rated") - buf;
3049                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                     next_out = leftover_start = i;
3051                     started = STARTED_CHATTER;
3052                     suppressKibitz = TRUE;
3053                     continue;
3054                 }
3055                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3056                         && looking_at(buf, &i, "* ads displayed")) {
3057                     soughtPending = FALSE;
3058                     seekGraphUp = TRUE;
3059                     DrawSeekGraph();
3060                     continue;
3061                 }
3062                 if(appData.autoRefresh) {
3063                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3064                         int s = (ics_type == ICS_ICC); // ICC format differs
3065                         if(seekGraphUp)
3066                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3067                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3068                         looking_at(buf, &i, "*% "); // eat prompt
3069                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3070                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3071                         next_out = i; // suppress
3072                         continue;
3073                     }
3074                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3075                         char *p = star_match[0];
3076                         while(*p) {
3077                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3078                             while(*p && *p++ != ' '); // next
3079                         }
3080                         looking_at(buf, &i, "*% "); // eat prompt
3081                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = i;
3083                         continue;
3084                     }
3085                 }
3086             }
3087
3088             /* skip formula vars */
3089             if (started == STARTED_NONE &&
3090                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3091               started = STARTED_CHATTER;
3092               i += 3;
3093               continue;
3094             }
3095
3096             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3097             if (appData.autoKibitz && started == STARTED_NONE &&
3098                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3099                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3100                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3101                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3102                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3103                         suppressKibitz = TRUE;
3104                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105                         next_out = i;
3106                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3107                                 && (gameMode == IcsPlayingWhite)) ||
3108                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3109                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3110                             started = STARTED_CHATTER; // own kibitz we simply discard
3111                         else {
3112                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3113                             parse_pos = 0; parse[0] = NULLCHAR;
3114                             savingComment = TRUE;
3115                             suppressKibitz = gameMode != IcsObserving ? 2 :
3116                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3117                         }
3118                         continue;
3119                 } else
3120                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3121                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3122                          && atoi(star_match[0])) {
3123                     // suppress the acknowledgements of our own autoKibitz
3124                     char *p;
3125                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3126                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3127                     SendToPlayer(star_match[0], strlen(star_match[0]));
3128                     if(looking_at(buf, &i, "*% ")) // eat prompt
3129                         suppressKibitz = FALSE;
3130                     next_out = i;
3131                     continue;
3132                 }
3133             } // [HGM] kibitz: end of patch
3134
3135             // [HGM] chat: intercept tells by users for which we have an open chat window
3136             channel = -1;
3137             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3138                                            looking_at(buf, &i, "* whispers:") ||
3139                                            looking_at(buf, &i, "* kibitzes:") ||
3140                                            looking_at(buf, &i, "* shouts:") ||
3141                                            looking_at(buf, &i, "* c-shouts:") ||
3142                                            looking_at(buf, &i, "--> * ") ||
3143                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3144                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3147                 int p;
3148                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3149                 chattingPartner = -1;
3150
3151                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3152                 for(p=0; p<MAX_CHAT; p++) {
3153                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3154                     talker[0] = '['; strcat(talker, "] ");
3155                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3156                     chattingPartner = p; break;
3157                     }
3158                 } else
3159                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3160                 for(p=0; p<MAX_CHAT; p++) {
3161                     if(!strcmp("kibitzes", chatPartner[p])) {
3162                         talker[0] = '['; strcat(talker, "] ");
3163                         chattingPartner = p; break;
3164                     }
3165                 } else
3166                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3167                 for(p=0; p<MAX_CHAT; p++) {
3168                     if(!strcmp("whispers", chatPartner[p])) {
3169                         talker[0] = '['; strcat(talker, "] ");
3170                         chattingPartner = p; break;
3171                     }
3172                 } else
3173                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3174                   if(buf[i-8] == '-' && buf[i-3] == 't')
3175                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3176                     if(!strcmp("c-shouts", chatPartner[p])) {
3177                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3178                         chattingPartner = p; break;
3179                     }
3180                   }
3181                   if(chattingPartner < 0)
3182                   for(p=0; p<MAX_CHAT; p++) {
3183                     if(!strcmp("shouts", chatPartner[p])) {
3184                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3185                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3186                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3187                         chattingPartner = p; break;
3188                     }
3189                   }
3190                 }
3191                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3192                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3193                     talker[0] = 0; Colorize(ColorTell, FALSE);
3194                     chattingPartner = p; break;
3195                 }
3196                 if(chattingPartner<0) i = oldi; else {
3197                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3198                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3199                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200                     started = STARTED_COMMENT;
3201                     parse_pos = 0; parse[0] = NULLCHAR;
3202                     savingComment = 3 + chattingPartner; // counts as TRUE
3203                     suppressKibitz = TRUE;
3204                     continue;
3205                 }
3206             } // [HGM] chat: end of patch
3207
3208           backup = i;
3209             if (appData.zippyTalk || appData.zippyPlay) {
3210                 /* [DM] Backup address for color zippy lines */
3211 #if ZIPPY
3212                if (loggedOn == TRUE)
3213                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3214                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3215 #endif
3216             } // [DM] 'else { ' deleted
3217                 if (
3218                     /* Regular tells and says */
3219                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3220                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3221                     looking_at(buf, &i, "* says: ") ||
3222                     /* Don't color "message" or "messages" output */
3223                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3224                     looking_at(buf, &i, "*. * at *:*: ") ||
3225                     looking_at(buf, &i, "--* (*:*): ") ||
3226                     /* Message notifications (same color as tells) */
3227                     looking_at(buf, &i, "* has left a message ") ||
3228                     looking_at(buf, &i, "* just sent you a message:\n") ||
3229                     /* Whispers and kibitzes */
3230                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3231                     looking_at(buf, &i, "* kibitzes: ") ||
3232                     /* Channel tells */
3233                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3234
3235                   if (tkind == 1 && strchr(star_match[0], ':')) {
3236                       /* Avoid "tells you:" spoofs in channels */
3237                      tkind = 3;
3238                   }
3239                   if (star_match[0][0] == NULLCHAR ||
3240                       strchr(star_match[0], ' ') ||
3241                       (tkind == 3 && strchr(star_match[1], ' '))) {
3242                     /* Reject bogus matches */
3243                     i = oldi;
3244                   } else {
3245                     if (appData.colorize) {
3246                       if (oldi > next_out) {
3247                         SendToPlayer(&buf[next_out], oldi - next_out);
3248                         next_out = oldi;
3249                       }
3250                       switch (tkind) {
3251                       case 1:
3252                         Colorize(ColorTell, FALSE);
3253                         curColor = ColorTell;
3254                         break;
3255                       case 2:
3256                         Colorize(ColorKibitz, FALSE);
3257                         curColor = ColorKibitz;
3258                         break;
3259                       case 3:
3260                         p = strrchr(star_match[1], '(');
3261                         if (p == NULL) {
3262                           p = star_match[1];
3263                         } else {
3264                           p++;
3265                         }
3266                         if (atoi(p) == 1) {
3267                           Colorize(ColorChannel1, FALSE);
3268                           curColor = ColorChannel1;
3269                         } else {
3270                           Colorize(ColorChannel, FALSE);
3271                           curColor = ColorChannel;
3272                         }
3273                         break;
3274                       case 5:
3275                         curColor = ColorNormal;
3276                         break;
3277                       }
3278                     }
3279                     if (started == STARTED_NONE && appData.autoComment &&
3280                         (gameMode == IcsObserving ||
3281                          gameMode == IcsPlayingWhite ||
3282                          gameMode == IcsPlayingBlack)) {
3283                       parse_pos = i - oldi;
3284                       memcpy(parse, &buf[oldi], parse_pos);
3285                       parse[parse_pos] = NULLCHAR;
3286                       started = STARTED_COMMENT;
3287                       savingComment = TRUE;
3288                     } else {
3289                       started = STARTED_CHATTER;
3290                       savingComment = FALSE;
3291                     }
3292                     loggedOn = TRUE;
3293                     continue;
3294                   }
3295                 }
3296
3297                 if (looking_at(buf, &i, "* s-shouts: ") ||
3298                     looking_at(buf, &i, "* c-shouts: ")) {
3299                     if (appData.colorize) {
3300                         if (oldi > next_out) {
3301                             SendToPlayer(&buf[next_out], oldi - next_out);
3302                             next_out = oldi;
3303                         }
3304                         Colorize(ColorSShout, FALSE);
3305                         curColor = ColorSShout;
3306                     }
3307                     loggedOn = TRUE;
3308                     started = STARTED_CHATTER;
3309                     continue;
3310                 }
3311
3312                 if (looking_at(buf, &i, "--->")) {
3313                     loggedOn = TRUE;
3314                     continue;
3315                 }
3316
3317                 if (looking_at(buf, &i, "* shouts: ") ||
3318                     looking_at(buf, &i, "--> ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorShout, FALSE);
3325                         curColor = ColorShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at( buf, &i, "Challenge:")) {
3333                     if (appData.colorize) {
3334                         if (oldi > next_out) {
3335                             SendToPlayer(&buf[next_out], oldi - next_out);
3336                             next_out = oldi;
3337                         }
3338                         Colorize(ColorChallenge, FALSE);
3339                         curColor = ColorChallenge;
3340                     }
3341                     loggedOn = TRUE;
3342                     continue;
3343                 }
3344
3345                 if (looking_at(buf, &i, "* offers you") ||
3346                     looking_at(buf, &i, "* offers to be") ||
3347                     looking_at(buf, &i, "* would like to") ||
3348                     looking_at(buf, &i, "* requests to") ||
3349                     looking_at(buf, &i, "Your opponent offers") ||
3350                     looking_at(buf, &i, "Your opponent requests")) {
3351
3352                     if (appData.colorize) {
3353                         if (oldi > next_out) {
3354                             SendToPlayer(&buf[next_out], oldi - next_out);
3355                             next_out = oldi;
3356                         }
3357                         Colorize(ColorRequest, FALSE);
3358                         curColor = ColorRequest;
3359                     }
3360                     continue;
3361                 }
3362
3363                 if (looking_at(buf, &i, "* (*) seeking")) {
3364                     if (appData.colorize) {
3365                         if (oldi > next_out) {
3366                             SendToPlayer(&buf[next_out], oldi - next_out);
3367                             next_out = oldi;
3368                         }
3369                         Colorize(ColorSeek, FALSE);
3370                         curColor = ColorSeek;
3371                     }
3372                     continue;
3373             }
3374
3375           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3376
3377             if (looking_at(buf, &i, "\\   ")) {
3378                 if (prevColor != ColorNormal) {
3379                     if (oldi > next_out) {
3380                         SendToPlayer(&buf[next_out], oldi - next_out);
3381                         next_out = oldi;
3382                     }
3383                     Colorize(prevColor, TRUE);
3384                     curColor = prevColor;
3385                 }
3386                 if (savingComment) {
3387                     parse_pos = i - oldi;
3388                     memcpy(parse, &buf[oldi], parse_pos);
3389                     parse[parse_pos] = NULLCHAR;
3390                     started = STARTED_COMMENT;
3391                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3392                         chattingPartner = savingComment - 3; // kludge to remember the box
3393                 } else {
3394                     started = STARTED_CHATTER;
3395                 }
3396                 continue;
3397             }
3398
3399             if (looking_at(buf, &i, "Black Strength :") ||
3400                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3401                 looking_at(buf, &i, "<10>") ||
3402                 looking_at(buf, &i, "#@#")) {
3403                 /* Wrong board style */
3404                 loggedOn = TRUE;
3405                 SendToICS(ics_prefix);
3406                 SendToICS("set style 12\n");
3407                 SendToICS(ics_prefix);
3408                 SendToICS("refresh\n");
3409                 continue;
3410             }
3411
3412             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3413                 ICSInitScript();
3414                 have_sent_ICS_logon = 1;
3415                 continue;
3416             }
3417
3418             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3419                 (looking_at(buf, &i, "\n<12> ") ||
3420                  looking_at(buf, &i, "<12> "))) {
3421                 loggedOn = TRUE;
3422                 if (oldi > next_out) {
3423                     SendToPlayer(&buf[next_out], oldi - next_out);
3424                 }
3425                 next_out = i;
3426                 started = STARTED_BOARD;
3427                 parse_pos = 0;
3428                 continue;
3429             }
3430
3431             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3432                 looking_at(buf, &i, "<b1> ")) {
3433                 if (oldi > next_out) {
3434                     SendToPlayer(&buf[next_out], oldi - next_out);
3435                 }
3436                 next_out = i;
3437                 started = STARTED_HOLDINGS;
3438                 parse_pos = 0;
3439                 continue;
3440             }
3441
3442             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3443                 loggedOn = TRUE;
3444                 /* Header for a move list -- first line */
3445
3446                 switch (ics_getting_history) {
3447                   case H_FALSE:
3448                     switch (gameMode) {
3449                       case IcsIdle:
3450                       case BeginningOfGame:
3451                         /* User typed "moves" or "oldmoves" while we
3452                            were idle.  Pretend we asked for these
3453                            moves and soak them up so user can step
3454                            through them and/or save them.
3455                            */
3456                         Reset(FALSE, TRUE);
3457                         gameMode = IcsObserving;
3458                         ModeHighlight();
3459                         ics_gamenum = -1;
3460                         ics_getting_history = H_GOT_UNREQ_HEADER;
3461                         break;
3462                       case EditGame: /*?*/
3463                       case EditPosition: /*?*/
3464                         /* Should above feature work in these modes too? */
3465                         /* For now it doesn't */
3466                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3467                         break;
3468                       default:
3469                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3470                         break;
3471                     }
3472                     break;
3473                   case H_REQUESTED:
3474                     /* Is this the right one? */
3475                     if (gameInfo.white && gameInfo.black &&
3476                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3477                         strcmp(gameInfo.black, star_match[2]) == 0) {
3478                         /* All is well */
3479                         ics_getting_history = H_GOT_REQ_HEADER;
3480                     }
3481                     break;
3482                   case H_GOT_REQ_HEADER:
3483                   case H_GOT_UNREQ_HEADER:
3484                   case H_GOT_UNWANTED_HEADER:
3485                   case H_GETTING_MOVES:
3486                     /* Should not happen */
3487                     DisplayError(_("Error gathering move list: two headers"), 0);
3488                     ics_getting_history = H_FALSE;
3489                     break;
3490                 }
3491
3492                 /* Save player ratings into gameInfo if needed */
3493                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3494                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3495                     (gameInfo.whiteRating == -1 ||
3496                      gameInfo.blackRating == -1)) {
3497
3498                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3499                     gameInfo.blackRating = string_to_rating(star_match[3]);
3500                     if (appData.debugMode)
3501                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3502                               gameInfo.whiteRating, gameInfo.blackRating);
3503                 }
3504                 continue;
3505             }
3506
3507             if (looking_at(buf, &i,
3508               "* * match, initial time: * minute*, increment: * second")) {
3509                 /* Header for a move list -- second line */
3510                 /* Initial board will follow if this is a wild game */
3511                 if (gameInfo.event != NULL) free(gameInfo.event);
3512                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3513                 gameInfo.event = StrSave(str);
3514                 /* [HGM] we switched variant. Translate boards if needed. */
3515                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3516                 continue;
3517             }
3518
3519             if (looking_at(buf, &i, "Move  ")) {
3520                 /* Beginning of a move list */
3521                 switch (ics_getting_history) {
3522                   case H_FALSE:
3523                     /* Normally should not happen */
3524                     /* Maybe user hit reset while we were parsing */
3525                     break;
3526                   case H_REQUESTED:
3527                     /* Happens if we are ignoring a move list that is not
3528                      * the one we just requested.  Common if the user
3529                      * tries to observe two games without turning off
3530                      * getMoveList */
3531                     break;
3532                   case H_GETTING_MOVES:
3533                     /* Should not happen */
3534                     DisplayError(_("Error gathering move list: nested"), 0);
3535                     ics_getting_history = H_FALSE;
3536                     break;
3537                   case H_GOT_REQ_HEADER:
3538                     ics_getting_history = H_GETTING_MOVES;
3539                     started = STARTED_MOVES;
3540                     parse_pos = 0;
3541                     if (oldi > next_out) {
3542                         SendToPlayer(&buf[next_out], oldi - next_out);
3543                     }
3544                     break;
3545                   case H_GOT_UNREQ_HEADER:
3546                     ics_getting_history = H_GETTING_MOVES;
3547                     started = STARTED_MOVES_NOHIDE;
3548                     parse_pos = 0;
3549                     break;
3550                   case H_GOT_UNWANTED_HEADER:
3551                     ics_getting_history = H_FALSE;
3552                     break;
3553                 }
3554                 continue;
3555             }
3556
3557             if (looking_at(buf, &i, "% ") ||
3558                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3559                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3560                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3561                     soughtPending = FALSE;
3562                     seekGraphUp = TRUE;
3563                     DrawSeekGraph();
3564                 }
3565                 if(suppressKibitz) next_out = i;
3566                 savingComment = FALSE;
3567                 suppressKibitz = 0;
3568                 switch (started) {
3569                   case STARTED_MOVES:
3570                   case STARTED_MOVES_NOHIDE:
3571                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3572                     parse[parse_pos + i - oldi] = NULLCHAR;
3573                     ParseGameHistory(parse);
3574 #if ZIPPY
3575                     if (appData.zippyPlay && first.initDone) {
3576                         FeedMovesToProgram(&first, forwardMostMove);
3577                         if (gameMode == IcsPlayingWhite) {
3578                             if (WhiteOnMove(forwardMostMove)) {
3579                                 if (first.sendTime) {
3580                                   if (first.useColors) {
3581                                     SendToProgram("black\n", &first);
3582                                   }
3583                                   SendTimeRemaining(&first, TRUE);
3584                                 }
3585                                 if (first.useColors) {
3586                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3587                                 }
3588                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3589                                 first.maybeThinking = TRUE;
3590                             } else {
3591                                 if (first.usePlayother) {
3592                                   if (first.sendTime) {
3593                                     SendTimeRemaining(&first, TRUE);
3594                                   }
3595                                   SendToProgram("playother\n", &first);
3596                                   firstMove = FALSE;
3597                                 } else {
3598                                   firstMove = TRUE;
3599                                 }
3600                             }
3601                         } else if (gameMode == IcsPlayingBlack) {
3602                             if (!WhiteOnMove(forwardMostMove)) {
3603                                 if (first.sendTime) {
3604                                   if (first.useColors) {
3605                                     SendToProgram("white\n", &first);
3606                                   }
3607                                   SendTimeRemaining(&first, FALSE);
3608                                 }
3609                                 if (first.useColors) {
3610                                   SendToProgram("black\n", &first);
3611                                 }
3612                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3613                                 first.maybeThinking = TRUE;
3614                             } else {
3615                                 if (first.usePlayother) {
3616                                   if (first.sendTime) {
3617                                     SendTimeRemaining(&first, FALSE);
3618                                   }
3619                                   SendToProgram("playother\n", &first);
3620                                   firstMove = FALSE;
3621                                 } else {
3622                                   firstMove = TRUE;
3623                                 }
3624                             }
3625                         }
3626                     }
3627 #endif
3628                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3629                         /* Moves came from oldmoves or moves command
3630                            while we weren't doing anything else.
3631                            */
3632                         currentMove = forwardMostMove;
3633                         ClearHighlights();/*!!could figure this out*/
3634                         flipView = appData.flipView;
3635                         DrawPosition(TRUE, boards[currentMove]);
3636                         DisplayBothClocks();
3637                         snprintf(str, MSG_SIZ, "%s vs. %s",
3638                                 gameInfo.white, gameInfo.black);
3639                         DisplayTitle(str);
3640                         gameMode = IcsIdle;
3641                     } else {
3642                         /* Moves were history of an active game */
3643                         if (gameInfo.resultDetails != NULL) {
3644                             free(gameInfo.resultDetails);
3645                             gameInfo.resultDetails = NULL;
3646                         }
3647                     }
3648                     HistorySet(parseList, backwardMostMove,
3649                                forwardMostMove, currentMove-1);
3650                     DisplayMove(currentMove - 1);
3651                     if (started == STARTED_MOVES) next_out = i;
3652                     started = STARTED_NONE;
3653                     ics_getting_history = H_FALSE;
3654                     break;
3655
3656                   case STARTED_OBSERVE:
3657                     started = STARTED_NONE;
3658                     SendToICS(ics_prefix);
3659                     SendToICS("refresh\n");
3660                     break;
3661
3662                   default:
3663                     break;
3664                 }
3665                 if(bookHit) { // [HGM] book: simulate book reply
3666                     static char bookMove[MSG_SIZ]; // a bit generous?
3667
3668                     programStats.nodes = programStats.depth = programStats.time =
3669                     programStats.score = programStats.got_only_move = 0;
3670                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3671
3672                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3673                     strcat(bookMove, bookHit);
3674                     HandleMachineMove(bookMove, &first);
3675                 }
3676                 continue;
3677             }
3678
3679             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3680                  started == STARTED_HOLDINGS ||
3681                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3682                 /* Accumulate characters in move list or board */
3683                 parse[parse_pos++] = buf[i];
3684             }
3685
3686             /* Start of game messages.  Mostly we detect start of game
3687                when the first board image arrives.  On some versions
3688                of the ICS, though, we need to do a "refresh" after starting
3689                to observe in order to get the current board right away. */
3690             if (looking_at(buf, &i, "Adding game * to observation list")) {
3691                 started = STARTED_OBSERVE;
3692                 continue;
3693             }
3694
3695             /* Handle auto-observe */
3696             if (appData.autoObserve &&
3697                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3698                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3699                 char *player;
3700                 /* Choose the player that was highlighted, if any. */
3701                 if (star_match[0][0] == '\033' ||
3702                     star_match[1][0] != '\033') {
3703                     player = star_match[0];
3704                 } else {
3705                     player = star_match[2];
3706                 }
3707                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3708                         ics_prefix, StripHighlightAndTitle(player));
3709                 SendToICS(str);
3710
3711                 /* Save ratings from notify string */
3712                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3713                 player1Rating = string_to_rating(star_match[1]);
3714                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3715                 player2Rating = string_to_rating(star_match[3]);
3716
3717                 if (appData.debugMode)
3718                   fprintf(debugFP,
3719                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3720                           player1Name, player1Rating,
3721                           player2Name, player2Rating);
3722
3723                 continue;
3724             }
3725
3726             /* Deal with automatic examine mode after a game,
3727                and with IcsObserving -> IcsExamining transition */
3728             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3729                 looking_at(buf, &i, "has made you an examiner of game *")) {
3730
3731                 int gamenum = atoi(star_match[0]);
3732                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3733                     gamenum == ics_gamenum) {
3734                     /* We were already playing or observing this game;
3735                        no need to refetch history */
3736                     gameMode = IcsExamining;
3737                     if (pausing) {
3738                         pauseExamForwardMostMove = forwardMostMove;
3739                     } else if (currentMove < forwardMostMove) {
3740                         ForwardInner(forwardMostMove);
3741                     }
3742                 } else {
3743                     /* I don't think this case really can happen */
3744                     SendToICS(ics_prefix);
3745                     SendToICS("refresh\n");
3746                 }
3747                 continue;
3748             }
3749
3750             /* Error messages */
3751 //          if (ics_user_moved) {
3752             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3753                 if (looking_at(buf, &i, "Illegal move") ||
3754                     looking_at(buf, &i, "Not a legal move") ||
3755                     looking_at(buf, &i, "Your king is in check") ||
3756                     looking_at(buf, &i, "It isn't your turn") ||
3757                     looking_at(buf, &i, "It is not your move")) {
3758                     /* Illegal move */
3759                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3760                         currentMove = forwardMostMove-1;
3761                         DisplayMove(currentMove - 1); /* before DMError */
3762                         DrawPosition(FALSE, boards[currentMove]);
3763                         SwitchClocks(forwardMostMove-1); // [HGM] race
3764                         DisplayBothClocks();
3765                     }
3766                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3767                     ics_user_moved = 0;
3768                     continue;
3769                 }
3770             }
3771
3772             if (looking_at(buf, &i, "still have time") ||
3773                 looking_at(buf, &i, "not out of time") ||
3774                 looking_at(buf, &i, "either player is out of time") ||
3775                 looking_at(buf, &i, "has timeseal; checking")) {
3776                 /* We must have called his flag a little too soon */
3777                 whiteFlag = blackFlag = FALSE;
3778                 continue;
3779             }
3780
3781             if (looking_at(buf, &i, "added * seconds to") ||
3782                 looking_at(buf, &i, "seconds were added to")) {
3783                 /* Update the clocks */
3784                 SendToICS(ics_prefix);
3785                 SendToICS("refresh\n");
3786                 continue;
3787             }
3788
3789             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3790                 ics_clock_paused = TRUE;
3791                 StopClocks();
3792                 continue;
3793             }
3794
3795             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3796                 ics_clock_paused = FALSE;
3797                 StartClocks();
3798                 continue;
3799             }
3800
3801             /* Grab player ratings from the Creating: message.
3802                Note we have to check for the special case when
3803                the ICS inserts things like [white] or [black]. */
3804             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3805                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3806                 /* star_matches:
3807                    0    player 1 name (not necessarily white)
3808                    1    player 1 rating
3809                    2    empty, white, or black (IGNORED)
3810                    3    player 2 name (not necessarily black)
3811                    4    player 2 rating
3812
3813                    The names/ratings are sorted out when the game
3814                    actually starts (below).
3815                 */
3816                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3817                 player1Rating = string_to_rating(star_match[1]);
3818                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3819                 player2Rating = string_to_rating(star_match[4]);
3820
3821                 if (appData.debugMode)
3822                   fprintf(debugFP,
3823                           "Ratings from 'Creating:' %s %d, %s %d\n",
3824                           player1Name, player1Rating,
3825                           player2Name, player2Rating);
3826
3827                 continue;
3828             }
3829
3830             /* Improved generic start/end-of-game messages */
3831             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3832                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3833                 /* If tkind == 0: */
3834                 /* star_match[0] is the game number */
3835                 /*           [1] is the white player's name */
3836                 /*           [2] is the black player's name */
3837                 /* For end-of-game: */
3838                 /*           [3] is the reason for the game end */
3839                 /*           [4] is a PGN end game-token, preceded by " " */
3840                 /* For start-of-game: */
3841                 /*           [3] begins with "Creating" or "Continuing" */
3842                 /*           [4] is " *" or empty (don't care). */
3843                 int gamenum = atoi(star_match[0]);
3844                 char *whitename, *blackname, *why, *endtoken;
3845                 ChessMove endtype = EndOfFile;
3846
3847                 if (tkind == 0) {
3848                   whitename = star_match[1];
3849                   blackname = star_match[2];
3850                   why = star_match[3];
3851                   endtoken = star_match[4];
3852                 } else {
3853                   whitename = star_match[1];
3854                   blackname = star_match[3];
3855                   why = star_match[5];
3856                   endtoken = star_match[6];
3857                 }
3858
3859                 /* Game start messages */
3860                 if (strncmp(why, "Creating ", 9) == 0 ||
3861                     strncmp(why, "Continuing ", 11) == 0) {
3862                     gs_gamenum = gamenum;
3863                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3864                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3865 #if ZIPPY
3866                     if (appData.zippyPlay) {
3867                         ZippyGameStart(whitename, blackname);
3868                     }
3869 #endif /*ZIPPY*/
3870                     partnerBoardValid = FALSE; // [HGM] bughouse
3871                     continue;
3872                 }
3873
3874                 /* Game end messages */
3875                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3876                     ics_gamenum != gamenum) {
3877                     continue;
3878                 }
3879                 while (endtoken[0] == ' ') endtoken++;
3880                 switch (endtoken[0]) {
3881                   case '*':
3882                   default:
3883                     endtype = GameUnfinished;
3884                     break;
3885                   case '0':
3886                     endtype = BlackWins;
3887                     break;
3888                   case '1':
3889                     if (endtoken[1] == '/')
3890                       endtype = GameIsDrawn;
3891                     else
3892                       endtype = WhiteWins;
3893                     break;
3894                 }
3895                 GameEnds(endtype, why, GE_ICS);
3896 #if ZIPPY
3897                 if (appData.zippyPlay && first.initDone) {
3898                     ZippyGameEnd(endtype, why);
3899                     if (first.pr == NULL) {
3900                       /* Start the next process early so that we'll
3901                          be ready for the next challenge */
3902                       StartChessProgram(&first);
3903                     }
3904                     /* Send "new" early, in case this command takes
3905                        a long time to finish, so that we'll be ready
3906                        for the next challenge. */
3907                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3908                     Reset(TRUE, TRUE);
3909                 }
3910 #endif /*ZIPPY*/
3911                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3912                 continue;
3913             }
3914
3915             if (looking_at(buf, &i, "Removing game * from observation") ||
3916                 looking_at(buf, &i, "no longer observing game *") ||
3917                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3918                 if (gameMode == IcsObserving &&
3919                     atoi(star_match[0]) == ics_gamenum)
3920                   {
3921                       /* icsEngineAnalyze */
3922                       if (appData.icsEngineAnalyze) {
3923                             ExitAnalyzeMode();
3924                             ModeHighlight();
3925                       }
3926                       StopClocks();
3927                       gameMode = IcsIdle;
3928                       ics_gamenum = -1;
3929                       ics_user_moved = FALSE;
3930                   }
3931                 continue;
3932             }
3933
3934             if (looking_at(buf, &i, "no longer examining game *")) {
3935                 if (gameMode == IcsExamining &&
3936                     atoi(star_match[0]) == ics_gamenum)
3937                   {
3938                       gameMode = IcsIdle;
3939                       ics_gamenum = -1;
3940                       ics_user_moved = FALSE;
3941                   }
3942                 continue;
3943             }
3944
3945             /* Advance leftover_start past any newlines we find,
3946                so only partial lines can get reparsed */
3947             if (looking_at(buf, &i, "\n")) {
3948                 prevColor = curColor;
3949                 if (curColor != ColorNormal) {
3950                     if (oldi > next_out) {
3951                         SendToPlayer(&buf[next_out], oldi - next_out);
3952                         next_out = oldi;
3953                     }
3954                     Colorize(ColorNormal, FALSE);
3955                     curColor = ColorNormal;
3956                 }
3957                 if (started == STARTED_BOARD) {
3958                     started = STARTED_NONE;
3959                     parse[parse_pos] = NULLCHAR;
3960                     ParseBoard12(parse);
3961                     ics_user_moved = 0;
3962
3963                     /* Send premove here */
3964                     if (appData.premove) {
3965                       char str[MSG_SIZ];
3966                       if (currentMove == 0 &&
3967                           gameMode == IcsPlayingWhite &&
3968                           appData.premoveWhite) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (currentMove == 1 &&
3974                                  gameMode == IcsPlayingBlack &&
3975                                  appData.premoveBlack) {
3976                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3977                         if (appData.debugMode)
3978                           fprintf(debugFP, "Sending premove:\n");
3979                         SendToICS(str);
3980                       } else if (gotPremove) {
3981                         gotPremove = 0;
3982                         ClearPremoveHighlights();
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                           UserMoveEvent(premoveFromX, premoveFromY,
3986                                         premoveToX, premoveToY,
3987                                         premovePromoChar);
3988                       }
3989                     }
3990
3991                     /* Usually suppress following prompt */
3992                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3993                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3994                         if (looking_at(buf, &i, "*% ")) {
3995                             savingComment = FALSE;
3996                             suppressKibitz = 0;
3997                         }
3998                     }
3999                     next_out = i;
4000                 } else if (started == STARTED_HOLDINGS) {
4001                     int gamenum;
4002                     char new_piece[MSG_SIZ];
4003                     started = STARTED_NONE;
4004                     parse[parse_pos] = NULLCHAR;
4005                     if (appData.debugMode)
4006                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4007                                                         parse, currentMove);
4008                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4009                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4010                         if (gameInfo.variant == VariantNormal) {
4011                           /* [HGM] We seem to switch variant during a game!
4012                            * Presumably no holdings were displayed, so we have
4013                            * to move the position two files to the right to
4014                            * create room for them!
4015                            */
4016                           VariantClass newVariant;
4017                           switch(gameInfo.boardWidth) { // base guess on board width
4018                                 case 9:  newVariant = VariantShogi; break;
4019                                 case 10: newVariant = VariantGreat; break;
4020                                 default: newVariant = VariantCrazyhouse; break;
4021                           }
4022                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4023                           /* Get a move list just to see the header, which
4024                              will tell us whether this is really bug or zh */
4025                           if (ics_getting_history == H_FALSE) {
4026                             ics_getting_history = H_REQUESTED;
4027                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4028                             SendToICS(str);
4029                           }
4030                         }
4031                         new_piece[0] = NULLCHAR;
4032                         sscanf(parse, "game %d white [%s black [%s <- %s",
4033                                &gamenum, white_holding, black_holding,
4034                                new_piece);
4035                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4036                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4037                         /* [HGM] copy holdings to board holdings area */
4038                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4039                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4040                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4041 #if ZIPPY
4042                         if (appData.zippyPlay && first.initDone) {
4043                             ZippyHoldings(white_holding, black_holding,
4044                                           new_piece);
4045                         }
4046 #endif /*ZIPPY*/
4047                         if (tinyLayout || smallLayout) {
4048                             char wh[16], bh[16];
4049                             PackHolding(wh, white_holding);
4050                             PackHolding(bh, black_holding);
4051                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4052                                     gameInfo.white, gameInfo.black);
4053                         } else {
4054                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4055                                     gameInfo.white, white_holding,
4056                                     gameInfo.black, black_holding);
4057                         }
4058                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4059                         DrawPosition(FALSE, boards[currentMove]);
4060                         DisplayTitle(str);
4061                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4062                         sscanf(parse, "game %d white [%s black [%s <- %s",
4063                                &gamenum, white_holding, black_holding,
4064                                new_piece);
4065                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4066                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4067                         /* [HGM] copy holdings to partner-board holdings area */
4068                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4069                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4070                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4071                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4072                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4073                       }
4074                     }
4075                     /* Suppress following prompt */
4076                     if (looking_at(buf, &i, "*% ")) {
4077                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4078                         savingComment = FALSE;
4079                         suppressKibitz = 0;
4080                     }
4081                     next_out = i;
4082                 }
4083                 continue;
4084             }
4085
4086             i++;                /* skip unparsed character and loop back */
4087         }
4088
4089         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4090 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4091 //          SendToPlayer(&buf[next_out], i - next_out);
4092             started != STARTED_HOLDINGS && leftover_start > next_out) {
4093             SendToPlayer(&buf[next_out], leftover_start - next_out);
4094             next_out = i;
4095         }
4096
4097         leftover_len = buf_len - leftover_start;
4098         /* if buffer ends with something we couldn't parse,
4099            reparse it after appending the next read */
4100
4101     } else if (count == 0) {
4102         RemoveInputSource(isr);
4103         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4104     } else {
4105         DisplayFatalError(_("Error reading from ICS"), error, 1);
4106     }
4107 }
4108
4109
4110 /* Board style 12 looks like this:
4111
4112    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4113
4114  * The "<12> " is stripped before it gets to this routine.  The two
4115  * trailing 0's (flip state and clock ticking) are later addition, and
4116  * some chess servers may not have them, or may have only the first.
4117  * Additional trailing fields may be added in the future.
4118  */
4119
4120 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4121
4122 #define RELATION_OBSERVING_PLAYED    0
4123 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4124 #define RELATION_PLAYING_MYMOVE      1
4125 #define RELATION_PLAYING_NOTMYMOVE  -1
4126 #define RELATION_EXAMINING           2
4127 #define RELATION_ISOLATED_BOARD     -3
4128 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4129
4130 void
4131 ParseBoard12(string)
4132      char *string;
4133 {
4134     GameMode newGameMode;
4135     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4136     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4137     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4138     char to_play, board_chars[200];
4139     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4140     char black[32], white[32];
4141     Board board;
4142     int prevMove = currentMove;
4143     int ticking = 2;
4144     ChessMove moveType;
4145     int fromX, fromY, toX, toY;
4146     char promoChar;
4147     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4148     char *bookHit = NULL; // [HGM] book
4149     Boolean weird = FALSE, reqFlag = FALSE;
4150
4151     fromX = fromY = toX = toY = -1;
4152
4153     newGame = FALSE;
4154
4155     if (appData.debugMode)
4156       fprintf(debugFP, _("Parsing board: %s\n"), string);
4157
4158     move_str[0] = NULLCHAR;
4159     elapsed_time[0] = NULLCHAR;
4160     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4161         int  i = 0, j;
4162         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4163             if(string[i] == ' ') { ranks++; files = 0; }
4164             else files++;
4165             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4166             i++;
4167         }
4168         for(j = 0; j <i; j++) board_chars[j] = string[j];
4169         board_chars[i] = '\0';
4170         string += i + 1;
4171     }
4172     n = sscanf(string, PATTERN, &to_play, &double_push,
4173                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4174                &gamenum, white, black, &relation, &basetime, &increment,
4175                &white_stren, &black_stren, &white_time, &black_time,
4176                &moveNum, str, elapsed_time, move_str, &ics_flip,
4177                &ticking);
4178
4179     if (n < 21) {
4180         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4181         DisplayError(str, 0);
4182         return;
4183     }
4184
4185     /* Convert the move number to internal form */
4186     moveNum = (moveNum - 1) * 2;
4187     if (to_play == 'B') moveNum++;
4188     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4189       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4190                         0, 1);
4191       return;
4192     }
4193
4194     switch (relation) {
4195       case RELATION_OBSERVING_PLAYED:
4196       case RELATION_OBSERVING_STATIC:
4197         if (gamenum == -1) {
4198             /* Old ICC buglet */
4199             relation = RELATION_OBSERVING_STATIC;
4200         }
4201         newGameMode = IcsObserving;
4202         break;
4203       case RELATION_PLAYING_MYMOVE:
4204       case RELATION_PLAYING_NOTMYMOVE:
4205         newGameMode =
4206           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4207             IcsPlayingWhite : IcsPlayingBlack;
4208         break;
4209       case RELATION_EXAMINING:
4210         newGameMode = IcsExamining;
4211         break;
4212       case RELATION_ISOLATED_BOARD:
4213       default:
4214         /* Just display this board.  If user was doing something else,
4215            we will forget about it until the next board comes. */
4216         newGameMode = IcsIdle;
4217         break;
4218       case RELATION_STARTING_POSITION:
4219         newGameMode = gameMode;
4220         break;
4221     }
4222
4223     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4224          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4225       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4226       char *toSqr;
4227       for (k = 0; k < ranks; k++) {
4228         for (j = 0; j < files; j++)
4229           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4230         if(gameInfo.holdingsWidth > 1) {
4231              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4232              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4233         }
4234       }
4235       CopyBoard(partnerBoard, board);
4236       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4237         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4238         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4239       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4240       if(toSqr = strchr(str, '-')) {
4241         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4242         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4243       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4244       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4245       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4246       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4247       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4248       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4249                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4250       DisplayMessage(partnerStatus, "");
4251         partnerBoardValid = TRUE;
4252       return;
4253     }
4254
4255     /* Modify behavior for initial board display on move listing
4256        of wild games.
4257        */
4258     switch (ics_getting_history) {
4259       case H_FALSE:
4260       case H_REQUESTED:
4261         break;
4262       case H_GOT_REQ_HEADER:
4263       case H_GOT_UNREQ_HEADER:
4264         /* This is the initial position of the current game */
4265         gamenum = ics_gamenum;
4266         moveNum = 0;            /* old ICS bug workaround */
4267         if (to_play == 'B') {
4268           startedFromSetupPosition = TRUE;
4269           blackPlaysFirst = TRUE;
4270           moveNum = 1;
4271           if (forwardMostMove == 0) forwardMostMove = 1;
4272           if (backwardMostMove == 0) backwardMostMove = 1;
4273           if (currentMove == 0) currentMove = 1;
4274         }
4275         newGameMode = gameMode;
4276         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4277         break;
4278       case H_GOT_UNWANTED_HEADER:
4279         /* This is an initial board that we don't want */
4280         return;
4281       case H_GETTING_MOVES:
4282         /* Should not happen */
4283         DisplayError(_("Error gathering move list: extra board"), 0);
4284         ics_getting_history = H_FALSE;
4285         return;
4286     }
4287
4288    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4289                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4290      /* [HGM] We seem to have switched variant unexpectedly
4291       * Try to guess new variant from board size
4292       */
4293           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4294           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4295           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4296           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4297           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4298           if(!weird) newVariant = VariantNormal;
4299           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4300           /* Get a move list just to see the header, which
4301              will tell us whether this is really bug or zh */
4302           if (ics_getting_history == H_FALSE) {
4303             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4304             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4305             SendToICS(str);
4306           }
4307     }
4308
4309     /* Take action if this is the first board of a new game, or of a
4310        different game than is currently being displayed.  */
4311     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4312         relation == RELATION_ISOLATED_BOARD) {
4313
4314         /* Forget the old game and get the history (if any) of the new one */
4315         if (gameMode != BeginningOfGame) {
4316           Reset(TRUE, TRUE);
4317         }
4318         newGame = TRUE;
4319         if (appData.autoRaiseBoard) BoardToTop();
4320         prevMove = -3;
4321         if (gamenum == -1) {
4322             newGameMode = IcsIdle;
4323         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4324                    appData.getMoveList && !reqFlag) {
4325             /* Need to get game history */
4326             ics_getting_history = H_REQUESTED;
4327             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4328             SendToICS(str);
4329         }
4330
4331         /* Initially flip the board to have black on the bottom if playing
4332            black or if the ICS flip flag is set, but let the user change
4333            it with the Flip View button. */
4334         flipView = appData.autoFlipView ?
4335           (newGameMode == IcsPlayingBlack) || ics_flip :
4336           appData.flipView;
4337
4338         /* Done with values from previous mode; copy in new ones */
4339         gameMode = newGameMode;
4340         ModeHighlight();
4341         ics_gamenum = gamenum;
4342         if (gamenum == gs_gamenum) {
4343             int klen = strlen(gs_kind);
4344             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4345             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4346             gameInfo.event = StrSave(str);
4347         } else {
4348             gameInfo.event = StrSave("ICS game");
4349         }
4350         gameInfo.site = StrSave(appData.icsHost);
4351         gameInfo.date = PGNDate();
4352         gameInfo.round = StrSave("-");
4353         gameInfo.white = StrSave(white);
4354         gameInfo.black = StrSave(black);
4355         timeControl = basetime * 60 * 1000;
4356         timeControl_2 = 0;
4357         timeIncrement = increment * 1000;
4358         movesPerSession = 0;
4359         gameInfo.timeControl = TimeControlTagValue();
4360         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4361   if (appData.debugMode) {
4362     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4363     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4364     setbuf(debugFP, NULL);
4365   }
4366
4367         gameInfo.outOfBook = NULL;
4368
4369         /* Do we have the ratings? */
4370         if (strcmp(player1Name, white) == 0 &&
4371             strcmp(player2Name, black) == 0) {
4372             if (appData.debugMode)
4373               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4374                       player1Rating, player2Rating);
4375             gameInfo.whiteRating = player1Rating;
4376             gameInfo.blackRating = player2Rating;
4377         } else if (strcmp(player2Name, white) == 0 &&
4378                    strcmp(player1Name, black) == 0) {
4379             if (appData.debugMode)
4380               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4381                       player2Rating, player1Rating);
4382             gameInfo.whiteRating = player2Rating;
4383             gameInfo.blackRating = player1Rating;
4384         }
4385         player1Name[0] = player2Name[0] = NULLCHAR;
4386
4387         /* Silence shouts if requested */
4388         if (appData.quietPlay &&
4389             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4390             SendToICS(ics_prefix);
4391             SendToICS("set shout 0\n");
4392         }
4393     }
4394
4395     /* Deal with midgame name changes */
4396     if (!newGame) {
4397         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4398             if (gameInfo.white) free(gameInfo.white);
4399             gameInfo.white = StrSave(white);
4400         }
4401         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4402             if (gameInfo.black) free(gameInfo.black);
4403             gameInfo.black = StrSave(black);
4404         }
4405     }
4406
4407     /* Throw away game result if anything actually changes in examine mode */
4408     if (gameMode == IcsExamining && !newGame) {
4409         gameInfo.result = GameUnfinished;
4410         if (gameInfo.resultDetails != NULL) {
4411             free(gameInfo.resultDetails);
4412             gameInfo.resultDetails = NULL;
4413         }
4414     }
4415
4416     /* In pausing && IcsExamining mode, we ignore boards coming
4417        in if they are in a different variation than we are. */
4418     if (pauseExamInvalid) return;
4419     if (pausing && gameMode == IcsExamining) {
4420         if (moveNum <= pauseExamForwardMostMove) {
4421             pauseExamInvalid = TRUE;
4422             forwardMostMove = pauseExamForwardMostMove;
4423             return;
4424         }
4425     }
4426
4427   if (appData.debugMode) {
4428     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4429   }
4430     /* Parse the board */
4431     for (k = 0; k < ranks; k++) {
4432       for (j = 0; j < files; j++)
4433         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4434       if(gameInfo.holdingsWidth > 1) {
4435            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4436            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4437       }
4438     }
4439     CopyBoard(boards[moveNum], board);
4440     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4441     if (moveNum == 0) {
4442         startedFromSetupPosition =
4443           !CompareBoards(board, initialPosition);
4444         if(startedFromSetupPosition)
4445             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4446     }
4447
4448     /* [HGM] Set castling rights. Take the outermost Rooks,
4449        to make it also work for FRC opening positions. Note that board12
4450        is really defective for later FRC positions, as it has no way to
4451        indicate which Rook can castle if they are on the same side of King.
4452        For the initial position we grant rights to the outermost Rooks,
4453        and remember thos rights, and we then copy them on positions
4454        later in an FRC game. This means WB might not recognize castlings with
4455        Rooks that have moved back to their original position as illegal,
4456        but in ICS mode that is not its job anyway.
4457     */
4458     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4459     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4460
4461         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4462             if(board[0][i] == WhiteRook) j = i;
4463         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4464         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4465             if(board[0][i] == WhiteRook) j = i;
4466         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4467         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4468             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4469         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4471             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4472         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473
4474         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4475         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4476             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4477         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4478             if(board[BOARD_HEIGHT-1][k] == bKing)
4479                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4480         if(gameInfo.variant == VariantTwoKings) {
4481             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4482             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4483             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4484         }
4485     } else { int r;
4486         r = boards[moveNum][CASTLING][0] = initialRights[0];
4487         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4488         r = boards[moveNum][CASTLING][1] = initialRights[1];
4489         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4490         r = boards[moveNum][CASTLING][3] = initialRights[3];
4491         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4492         r = boards[moveNum][CASTLING][4] = initialRights[4];
4493         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4494         /* wildcastle kludge: always assume King has rights */
4495         r = boards[moveNum][CASTLING][2] = initialRights[2];
4496         r = boards[moveNum][CASTLING][5] = initialRights[5];
4497     }
4498     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4499     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4500
4501
4502     if (ics_getting_history == H_GOT_REQ_HEADER ||
4503         ics_getting_history == H_GOT_UNREQ_HEADER) {
4504         /* This was an initial position from a move list, not
4505            the current position */
4506         return;
4507     }
4508
4509     /* Update currentMove and known move number limits */
4510     newMove = newGame || moveNum > forwardMostMove;
4511
4512     if (newGame) {
4513         forwardMostMove = backwardMostMove = currentMove = moveNum;
4514         if (gameMode == IcsExamining && moveNum == 0) {
4515           /* Workaround for ICS limitation: we are not told the wild
4516              type when starting to examine a game.  But if we ask for
4517              the move list, the move list header will tell us */
4518             ics_getting_history = H_REQUESTED;
4519             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4520             SendToICS(str);
4521         }
4522     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4523                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4524 #if ZIPPY
4525         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4526         /* [HGM] applied this also to an engine that is silently watching        */
4527         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4528             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4529             gameInfo.variant == currentlyInitializedVariant) {
4530           takeback = forwardMostMove - moveNum;
4531           for (i = 0; i < takeback; i++) {
4532             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4533             SendToProgram("undo\n", &first);
4534           }
4535         }
4536 #endif
4537
4538         forwardMostMove = moveNum;
4539         if (!pausing || currentMove > forwardMostMove)
4540           currentMove = forwardMostMove;
4541     } else {
4542         /* New part of history that is not contiguous with old part */
4543         if (pausing && gameMode == IcsExamining) {
4544             pauseExamInvalid = TRUE;
4545             forwardMostMove = pauseExamForwardMostMove;
4546             return;
4547         }
4548         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4549 #if ZIPPY
4550             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4551                 // [HGM] when we will receive the move list we now request, it will be
4552                 // fed to the engine from the first move on. So if the engine is not
4553                 // in the initial position now, bring it there.
4554                 InitChessProgram(&first, 0);
4555             }
4556 #endif
4557             ics_getting_history = H_REQUESTED;
4558             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4559             SendToICS(str);
4560         }
4561         forwardMostMove = backwardMostMove = currentMove = moveNum;
4562     }
4563
4564     /* Update the clocks */
4565     if (strchr(elapsed_time, '.')) {
4566       /* Time is in ms */
4567       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4568       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4569     } else {
4570       /* Time is in seconds */
4571       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4572       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4573     }
4574
4575
4576 #if ZIPPY
4577     if (appData.zippyPlay && newGame &&
4578         gameMode != IcsObserving && gameMode != IcsIdle &&
4579         gameMode != IcsExamining)
4580       ZippyFirstBoard(moveNum, basetime, increment);
4581 #endif
4582
4583     /* Put the move on the move list, first converting
4584        to canonical algebraic form. */
4585     if (moveNum > 0) {
4586   if (appData.debugMode) {
4587     if (appData.debugMode) { int f = forwardMostMove;
4588         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4589                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4590                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4591     }
4592     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4593     fprintf(debugFP, "moveNum = %d\n", moveNum);
4594     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4595     setbuf(debugFP, NULL);
4596   }
4597         if (moveNum <= backwardMostMove) {
4598             /* We don't know what the board looked like before
4599                this move.  Punt. */
4600           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4601             strcat(parseList[moveNum - 1], " ");
4602             strcat(parseList[moveNum - 1], elapsed_time);
4603             moveList[moveNum - 1][0] = NULLCHAR;
4604         } else if (strcmp(move_str, "none") == 0) {
4605             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4606             /* Again, we don't know what the board looked like;
4607                this is really the start of the game. */
4608             parseList[moveNum - 1][0] = NULLCHAR;
4609             moveList[moveNum - 1][0] = NULLCHAR;
4610             backwardMostMove = moveNum;
4611             startedFromSetupPosition = TRUE;
4612             fromX = fromY = toX = toY = -1;
4613         } else {
4614           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4615           //                 So we parse the long-algebraic move string in stead of the SAN move
4616           int valid; char buf[MSG_SIZ], *prom;
4617
4618           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4619                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4620           // str looks something like "Q/a1-a2"; kill the slash
4621           if(str[1] == '/')
4622             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4623           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4624           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4625                 strcat(buf, prom); // long move lacks promo specification!
4626           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4627                 if(appData.debugMode)
4628                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4629                 safeStrCpy(move_str, buf, MSG_SIZ);
4630           }
4631           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4632                                 &fromX, &fromY, &toX, &toY, &promoChar)
4633                || ParseOneMove(buf, moveNum - 1, &moveType,
4634                                 &fromX, &fromY, &toX, &toY, &promoChar);
4635           // end of long SAN patch
4636           if (valid) {
4637             (void) CoordsToAlgebraic(boards[moveNum - 1],
4638                                      PosFlags(moveNum - 1),
4639                                      fromY, fromX, toY, toX, promoChar,
4640                                      parseList[moveNum-1]);
4641             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4642               case MT_NONE:
4643               case MT_STALEMATE:
4644               default:
4645                 break;
4646               case MT_CHECK:
4647                 if(gameInfo.variant != VariantShogi)
4648                     strcat(parseList[moveNum - 1], "+");
4649                 break;
4650               case MT_CHECKMATE:
4651               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4652                 strcat(parseList[moveNum - 1], "#");
4653                 break;
4654             }
4655             strcat(parseList[moveNum - 1], " ");
4656             strcat(parseList[moveNum - 1], elapsed_time);
4657             /* currentMoveString is set as a side-effect of ParseOneMove */
4658             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4659             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4660             strcat(moveList[moveNum - 1], "\n");
4661
4662             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4663                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4664               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4665                 ChessSquare old, new = boards[moveNum][k][j];
4666                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4667                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4668                   if(old == new) continue;
4669                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4670                   else if(new == WhiteWazir || new == BlackWazir) {
4671                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4672                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4673                       else boards[moveNum][k][j] = old; // preserve type of Gold
4674                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4675                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4676               }
4677           } else {
4678             /* Move from ICS was illegal!?  Punt. */
4679             if (appData.debugMode) {
4680               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4681               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4682             }
4683             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4684             strcat(parseList[moveNum - 1], " ");
4685             strcat(parseList[moveNum - 1], elapsed_time);
4686             moveList[moveNum - 1][0] = NULLCHAR;
4687             fromX = fromY = toX = toY = -1;
4688           }
4689         }
4690   if (appData.debugMode) {
4691     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4692     setbuf(debugFP, NULL);
4693   }
4694
4695 #if ZIPPY
4696         /* Send move to chess program (BEFORE animating it). */
4697         if (appData.zippyPlay && !newGame && newMove &&
4698            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4699
4700             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4701                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4702                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4703                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4704                             move_str);
4705                     DisplayError(str, 0);
4706                 } else {
4707                     if (first.sendTime) {
4708                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4709                     }
4710                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4711                     if (firstMove && !bookHit) {
4712                         firstMove = FALSE;
4713                         if (first.useColors) {
4714                           SendToProgram(gameMode == IcsPlayingWhite ?
4715                                         "white\ngo\n" :
4716                                         "black\ngo\n", &first);
4717                         } else {
4718                           SendToProgram("go\n", &first);
4719                         }
4720                         first.maybeThinking = TRUE;
4721                     }
4722                 }
4723             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4724               if (moveList[moveNum - 1][0] == NULLCHAR) {
4725                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4726                 DisplayError(str, 0);
4727               } else {
4728                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4729                 SendMoveToProgram(moveNum - 1, &first);
4730               }
4731             }
4732         }
4733 #endif
4734     }
4735
4736     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4737         /* If move comes from a remote source, animate it.  If it
4738            isn't remote, it will have already been animated. */
4739         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4740             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4741         }
4742         if (!pausing && appData.highlightLastMove) {
4743             SetHighlights(fromX, fromY, toX, toY);
4744         }
4745     }
4746
4747     /* Start the clocks */
4748     whiteFlag = blackFlag = FALSE;
4749     appData.clockMode = !(basetime == 0 && increment == 0);
4750     if (ticking == 0) {
4751       ics_clock_paused = TRUE;
4752       StopClocks();
4753     } else if (ticking == 1) {
4754       ics_clock_paused = FALSE;
4755     }
4756     if (gameMode == IcsIdle ||
4757         relation == RELATION_OBSERVING_STATIC ||
4758         relation == RELATION_EXAMINING ||
4759         ics_clock_paused)
4760       DisplayBothClocks();
4761     else
4762       StartClocks();
4763
4764     /* Display opponents and material strengths */
4765     if (gameInfo.variant != VariantBughouse &&
4766         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4767         if (tinyLayout || smallLayout) {
4768             if(gameInfo.variant == VariantNormal)
4769               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4770                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4771                     basetime, increment);
4772             else
4773               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment, (int) gameInfo.variant);
4776         } else {
4777             if(gameInfo.variant == VariantNormal)
4778               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4779                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4780                     basetime, increment);
4781             else
4782               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4783                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4784                     basetime, increment, VariantName(gameInfo.variant));
4785         }
4786         DisplayTitle(str);
4787   if (appData.debugMode) {
4788     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4789   }
4790     }
4791
4792
4793     /* Display the board */
4794     if (!pausing && !appData.noGUI) {
4795
4796       if (appData.premove)
4797           if (!gotPremove ||
4798              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4799              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4800               ClearPremoveHighlights();
4801
4802       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4803         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4804       DrawPosition(j, boards[currentMove]);
4805
4806       DisplayMove(moveNum - 1);
4807       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4808             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4809               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4810         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4811       }
4812     }
4813
4814     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4815 #if ZIPPY
4816     if(bookHit) { // [HGM] book: simulate book reply
4817         static char bookMove[MSG_SIZ]; // a bit generous?
4818
4819         programStats.nodes = programStats.depth = programStats.time =
4820         programStats.score = programStats.got_only_move = 0;
4821         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4822
4823         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4824         strcat(bookMove, bookHit);
4825         HandleMachineMove(bookMove, &first);
4826     }
4827 #endif
4828 }
4829
4830 void
4831 GetMoveListEvent()
4832 {
4833     char buf[MSG_SIZ];
4834     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4835         ics_getting_history = H_REQUESTED;
4836         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4837         SendToICS(buf);
4838     }
4839 }
4840
4841 void
4842 AnalysisPeriodicEvent(force)
4843      int force;
4844 {
4845     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4846          && !force) || !appData.periodicUpdates)
4847       return;
4848
4849     /* Send . command to Crafty to collect stats */
4850     SendToProgram(".\n", &first);
4851
4852     /* Don't send another until we get a response (this makes
4853        us stop sending to old Crafty's which don't understand
4854        the "." command (sending illegal cmds resets node count & time,
4855        which looks bad)) */
4856     programStats.ok_to_send = 0;
4857 }
4858
4859 void ics_update_width(new_width)
4860         int new_width;
4861 {
4862         ics_printf("set width %d\n", new_width);
4863 }
4864
4865 void
4866 SendMoveToProgram(moveNum, cps)
4867      int moveNum;
4868      ChessProgramState *cps;
4869 {
4870     char buf[MSG_SIZ];
4871
4872     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4873         // null move in variant where engine does not understand it (for analysis purposes)
4874         SendBoard(cps, moveNum + 1); // send position after move in stead.
4875         return;
4876     }
4877     if (cps->useUsermove) {
4878       SendToProgram("usermove ", cps);
4879     }
4880     if (cps->useSAN) {
4881       char *space;
4882       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4883         int len = space - parseList[moveNum];
4884         memcpy(buf, parseList[moveNum], len);
4885         buf[len++] = '\n';
4886         buf[len] = NULLCHAR;
4887       } else {
4888         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4889       }
4890       SendToProgram(buf, cps);
4891     } else {
4892       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4893         AlphaRank(moveList[moveNum], 4);
4894         SendToProgram(moveList[moveNum], cps);
4895         AlphaRank(moveList[moveNum], 4); // and back
4896       } else
4897       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4898        * the engine. It would be nice to have a better way to identify castle
4899        * moves here. */
4900       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4901                                                                          && cps->useOOCastle) {
4902         int fromX = moveList[moveNum][0] - AAA;
4903         int fromY = moveList[moveNum][1] - ONE;
4904         int toX = moveList[moveNum][2] - AAA;
4905         int toY = moveList[moveNum][3] - ONE;
4906         if((boards[moveNum][fromY][fromX] == WhiteKing
4907             && boards[moveNum][toY][toX] == WhiteRook)
4908            || (boards[moveNum][fromY][fromX] == BlackKing
4909                && boards[moveNum][toY][toX] == BlackRook)) {
4910           if(toX > fromX) SendToProgram("O-O\n", cps);
4911           else SendToProgram("O-O-O\n", cps);
4912         }
4913         else SendToProgram(moveList[moveNum], cps);
4914       } else
4915       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4916         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4917           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4918           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4919                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4920         } else
4921           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4922                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4923         SendToProgram(buf, cps);
4924       }
4925       else SendToProgram(moveList[moveNum], cps);
4926       /* End of additions by Tord */
4927     }
4928
4929     /* [HGM] setting up the opening has brought engine in force mode! */
4930     /*       Send 'go' if we are in a mode where machine should play. */
4931     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4932         (gameMode == TwoMachinesPlay   ||
4933 #if ZIPPY
4934          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4935 #endif
4936          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4937         SendToProgram("go\n", cps);
4938   if (appData.debugMode) {
4939     fprintf(debugFP, "(extra)\n");
4940   }
4941     }
4942     setboardSpoiledMachineBlack = 0;
4943 }
4944
4945 void
4946 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4947      ChessMove moveType;
4948      int fromX, fromY, toX, toY;
4949      char promoChar;
4950 {
4951     char user_move[MSG_SIZ];
4952
4953     switch (moveType) {
4954       default:
4955         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4956                 (int)moveType, fromX, fromY, toX, toY);
4957         DisplayError(user_move + strlen("say "), 0);
4958         break;
4959       case WhiteKingSideCastle:
4960       case BlackKingSideCastle:
4961       case WhiteQueenSideCastleWild:
4962       case BlackQueenSideCastleWild:
4963       /* PUSH Fabien */
4964       case WhiteHSideCastleFR:
4965       case BlackHSideCastleFR:
4966       /* POP Fabien */
4967         snprintf(user_move, MSG_SIZ, "o-o\n");
4968         break;
4969       case WhiteQueenSideCastle:
4970       case BlackQueenSideCastle:
4971       case WhiteKingSideCastleWild:
4972       case BlackKingSideCastleWild:
4973       /* PUSH Fabien */
4974       case WhiteASideCastleFR:
4975       case BlackASideCastleFR:
4976       /* POP Fabien */
4977         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4978         break;
4979       case WhiteNonPromotion:
4980       case BlackNonPromotion:
4981         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4982         break;
4983       case WhitePromotion:
4984       case BlackPromotion:
4985         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4986           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4987                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4988                 PieceToChar(WhiteFerz));
4989         else if(gameInfo.variant == VariantGreat)
4990           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4991                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4992                 PieceToChar(WhiteMan));
4993         else
4994           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4995                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4996                 promoChar);
4997         break;
4998       case WhiteDrop:
4999       case BlackDrop:
5000       drop:
5001         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5002                  ToUpper(PieceToChar((ChessSquare) fromX)),
5003                  AAA + toX, ONE + toY);
5004         break;
5005       case IllegalMove:  /* could be a variant we don't quite understand */
5006         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5007       case NormalMove:
5008       case WhiteCapturesEnPassant:
5009       case BlackCapturesEnPassant:
5010         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5011                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5012         break;
5013     }
5014     SendToICS(user_move);
5015     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5016         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5017 }
5018
5019 void
5020 UploadGameEvent()
5021 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5022     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5023     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5024     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5025         DisplayError("You cannot do this while you are playing or observing", 0);
5026         return;
5027     }
5028     if(gameMode != IcsExamining) { // is this ever not the case?
5029         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5030
5031         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5032           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5033         } else { // on FICS we must first go to general examine mode
5034           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5035         }
5036         if(gameInfo.variant != VariantNormal) {
5037             // try figure out wild number, as xboard names are not always valid on ICS
5038             for(i=1; i<=36; i++) {
5039               snprintf(buf, MSG_SIZ, "wild/%d", i);
5040                 if(StringToVariant(buf) == gameInfo.variant) break;
5041             }
5042             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5043             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5044             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5045         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5046         SendToICS(ics_prefix);
5047         SendToICS(buf);
5048         if(startedFromSetupPosition || backwardMostMove != 0) {
5049           fen = PositionToFEN(backwardMostMove, NULL);
5050           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5051             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5052             SendToICS(buf);
5053           } else { // FICS: everything has to set by separate bsetup commands
5054             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5055             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5056             SendToICS(buf);
5057             if(!WhiteOnMove(backwardMostMove)) {
5058                 SendToICS("bsetup tomove black\n");
5059             }
5060             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5061             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5062             SendToICS(buf);
5063             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5064             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5065             SendToICS(buf);
5066             i = boards[backwardMostMove][EP_STATUS];
5067             if(i >= 0) { // set e.p.
5068               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5069                 SendToICS(buf);
5070             }
5071             bsetup++;
5072           }
5073         }
5074       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5075             SendToICS("bsetup done\n"); // switch to normal examining.
5076     }
5077     for(i = backwardMostMove; i<last; i++) {
5078         char buf[20];
5079         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5080         SendToICS(buf);
5081     }
5082     SendToICS(ics_prefix);
5083     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5084 }
5085
5086 void
5087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5088      int rf, ff, rt, ft;
5089      char promoChar;
5090      char move[7];
5091 {
5092     if (rf == DROP_RANK) {
5093       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5094       sprintf(move, "%c@%c%c\n",
5095                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5096     } else {
5097         if (promoChar == 'x' || promoChar == NULLCHAR) {
5098           sprintf(move, "%c%c%c%c\n",
5099                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5100         } else {
5101             sprintf(move, "%c%c%c%c%c\n",
5102                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5103         }
5104     }
5105 }
5106
5107 void
5108 ProcessICSInitScript(f)
5109      FILE *f;
5110 {
5111     char buf[MSG_SIZ];
5112
5113     while (fgets(buf, MSG_SIZ, f)) {
5114         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5115     }
5116
5117     fclose(f);
5118 }
5119
5120
5121 static int lastX, lastY, selectFlag, dragging;
5122
5123 void
5124 Sweep(int step)
5125 {
5126     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5127     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5128     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5129     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5130     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5131     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5132     do {
5133         promoSweep -= step;
5134         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5135         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5136         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5137         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5138         if(!step) step = 1;
5139     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5140             appData.testLegality && (promoSweep == king ||
5141             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5142     ChangeDragPiece(promoSweep);
5143 }
5144
5145 int PromoScroll(int x, int y)
5146 {
5147   int step = 0;
5148
5149   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5150   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5151   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5152   if(!step) return FALSE;
5153   lastX = x; lastY = y;
5154   if((promoSweep < BlackPawn) == flipView) step = -step;
5155   if(step > 0) selectFlag = 1;
5156   if(!selectFlag) Sweep(step);
5157   return FALSE;
5158 }
5159
5160 void
5161 NextPiece(int step)
5162 {
5163     ChessSquare piece = boards[currentMove][toY][toX];
5164     do {
5165         pieceSweep -= step;
5166         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5167         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5168         if(!step) step = -1;
5169     } while(PieceToChar(pieceSweep) == '.');
5170     boards[currentMove][toY][toX] = pieceSweep;
5171     DrawPosition(FALSE, boards[currentMove]);
5172     boards[currentMove][toY][toX] = piece;
5173 }
5174 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5175 void
5176 AlphaRank(char *move, int n)
5177 {
5178 //    char *p = move, c; int x, y;
5179
5180     if (appData.debugMode) {
5181         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5182     }
5183
5184     if(move[1]=='*' &&
5185        move[2]>='0' && move[2]<='9' &&
5186        move[3]>='a' && move[3]<='x'    ) {
5187         move[1] = '@';
5188         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5189         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5190     } else
5191     if(move[0]>='0' && move[0]<='9' &&
5192        move[1]>='a' && move[1]<='x' &&
5193        move[2]>='0' && move[2]<='9' &&
5194        move[3]>='a' && move[3]<='x'    ) {
5195         /* input move, Shogi -> normal */
5196         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5197         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5198         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5199         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5200     } else
5201     if(move[1]=='@' &&
5202        move[3]>='0' && move[3]<='9' &&
5203        move[2]>='a' && move[2]<='x'    ) {
5204         move[1] = '*';
5205         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5206         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5207     } else
5208     if(
5209        move[0]>='a' && move[0]<='x' &&
5210        move[3]>='0' && move[3]<='9' &&
5211        move[2]>='a' && move[2]<='x'    ) {
5212          /* output move, normal -> Shogi */
5213         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5214         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5215         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5216         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5217         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5218     }
5219     if (appData.debugMode) {
5220         fprintf(debugFP, "   out = '%s'\n", move);
5221     }
5222 }
5223
5224 char yy_textstr[8000];
5225
5226 /* Parser for moves from gnuchess, ICS, or user typein box */
5227 Boolean
5228 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5229      char *move;
5230      int moveNum;
5231      ChessMove *moveType;
5232      int *fromX, *fromY, *toX, *toY;
5233      char *promoChar;
5234 {
5235     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5236
5237     switch (*moveType) {
5238       case WhitePromotion:
5239       case BlackPromotion:
5240       case WhiteNonPromotion:
5241       case BlackNonPromotion:
5242       case NormalMove:
5243       case WhiteCapturesEnPassant:
5244       case BlackCapturesEnPassant:
5245       case WhiteKingSideCastle:
5246       case WhiteQueenSideCastle:
5247       case BlackKingSideCastle:
5248       case BlackQueenSideCastle:
5249       case WhiteKingSideCastleWild:
5250       case WhiteQueenSideCastleWild:
5251       case BlackKingSideCastleWild:
5252       case BlackQueenSideCastleWild:
5253       /* Code added by Tord: */
5254       case WhiteHSideCastleFR:
5255       case WhiteASideCastleFR:
5256       case BlackHSideCastleFR:
5257       case BlackASideCastleFR:
5258       /* End of code added by Tord */
5259       case IllegalMove:         /* bug or odd chess variant */
5260         *fromX = currentMoveString[0] - AAA;
5261         *fromY = currentMoveString[1] - ONE;
5262         *toX = currentMoveString[2] - AAA;
5263         *toY = currentMoveString[3] - ONE;
5264         *promoChar = currentMoveString[4];
5265         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5266             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5267     if (appData.debugMode) {
5268         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5269     }
5270             *fromX = *fromY = *toX = *toY = 0;
5271             return FALSE;
5272         }
5273         if (appData.testLegality) {
5274           return (*moveType != IllegalMove);
5275         } else {
5276           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5277                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5278         }
5279
5280       case WhiteDrop:
5281       case BlackDrop:
5282         *fromX = *moveType == WhiteDrop ?
5283           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5284           (int) CharToPiece(ToLower(currentMoveString[0]));
5285         *fromY = DROP_RANK;
5286         *toX = currentMoveString[2] - AAA;
5287         *toY = currentMoveString[3] - ONE;
5288         *promoChar = NULLCHAR;
5289         return TRUE;
5290
5291       case AmbiguousMove:
5292       case ImpossibleMove:
5293       case EndOfFile:
5294       case ElapsedTime:
5295       case Comment:
5296       case PGNTag:
5297       case NAG:
5298       case WhiteWins:
5299       case BlackWins:
5300       case GameIsDrawn:
5301       default:
5302     if (appData.debugMode) {
5303         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5304     }
5305         /* bug? */
5306         *fromX = *fromY = *toX = *toY = 0;
5307         *promoChar = NULLCHAR;
5308         return FALSE;
5309     }
5310 }
5311
5312 Boolean pushed = FALSE;
5313 char *lastParseAttempt;
5314
5315 void
5316 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5317 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5318   int fromX, fromY, toX, toY; char promoChar;
5319   ChessMove moveType;
5320   Boolean valid;
5321   int nr = 0;
5322
5323   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5324     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5325     pushed = TRUE;
5326   }
5327   endPV = forwardMostMove;
5328   do {
5329     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5330     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5331     lastParseAttempt = pv;
5332     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5333 if(appData.debugMode){
5334 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5335 }
5336     if(!valid && nr == 0 &&
5337        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5338         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5339         // Hande case where played move is different from leading PV move
5340         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5341         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5342         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5343         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5344           endPV += 2; // if position different, keep this
5345           moveList[endPV-1][0] = fromX + AAA;
5346           moveList[endPV-1][1] = fromY + ONE;
5347           moveList[endPV-1][2] = toX + AAA;
5348           moveList[endPV-1][3] = toY + ONE;
5349           parseList[endPV-1][0] = NULLCHAR;
5350           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5351         }
5352       }
5353     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5354     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5355     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5356     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5357         valid++; // allow comments in PV
5358         continue;
5359     }
5360     nr++;
5361     if(endPV+1 > framePtr) break; // no space, truncate
5362     if(!valid) break;
5363     endPV++;
5364     CopyBoard(boards[endPV], boards[endPV-1]);
5365     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5366     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5367     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5368     CoordsToAlgebraic(boards[endPV - 1],
5369                              PosFlags(endPV - 1),
5370                              fromY, fromX, toY, toX, promoChar,
5371                              parseList[endPV - 1]);
5372   } while(valid);
5373   if(atEnd == 2) return; // used hidden, for PV conversion
5374   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5375   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5376   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5377                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5378   DrawPosition(TRUE, boards[currentMove]);
5379 }
5380
5381 int
5382 MultiPV(ChessProgramState *cps)
5383 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5384         int i;
5385         for(i=0; i<cps->nrOptions; i++)
5386             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5387                 return i;
5388         return -1;
5389 }
5390
5391 Boolean
5392 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5393 {
5394         int startPV, multi, lineStart, origIndex = index;
5395         char *p, buf2[MSG_SIZ];
5396
5397         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5398         lastX = x; lastY = y;
5399         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5400         lineStart = startPV = index;
5401         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5402         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5403         index = startPV;
5404         do{ while(buf[index] && buf[index] != '\n') index++;
5405         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5406         buf[index] = 0;
5407         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5408                 int n = first.option[multi].value;
5409                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5410                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5411                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5412                 first.option[multi].value = n;
5413                 *start = *end = 0;
5414                 return FALSE;
5415         }
5416         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5417         *start = startPV; *end = index-1;
5418         return TRUE;
5419 }
5420
5421 char *
5422 PvToSAN(char *pv)
5423 {
5424         static char buf[10*MSG_SIZ];
5425         int i, k=0, savedEnd=endPV;
5426         *buf = NULLCHAR;
5427         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5428         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5429         for(i = forwardMostMove; i<endPV; i++){
5430             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5431             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5432             k += strlen(buf+k);
5433         }
5434         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5435         if(forwardMostMove < savedEnd) PopInner(0);
5436         endPV = savedEnd;
5437         return buf;
5438 }
5439
5440 Boolean
5441 LoadPV(int x, int y)
5442 { // called on right mouse click to load PV
5443   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5444   lastX = x; lastY = y;
5445   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5446   return TRUE;
5447 }
5448
5449 void
5450 UnLoadPV()
5451 {
5452   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5453   if(endPV < 0) return;
5454   endPV = -1;
5455   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5456         Boolean saveAnimate = appData.animate;
5457         if(pushed) {
5458             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5459                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5460             } else storedGames--; // abandon shelved tail of original game
5461         }
5462         pushed = FALSE;
5463         forwardMostMove = currentMove;
5464         currentMove = oldFMM;
5465         appData.animate = FALSE;
5466         ToNrEvent(forwardMostMove);
5467         appData.animate = saveAnimate;
5468   }
5469   currentMove = forwardMostMove;
5470   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5471   ClearPremoveHighlights();
5472   DrawPosition(TRUE, boards[currentMove]);
5473 }
5474
5475 void
5476 MovePV(int x, int y, int h)
5477 { // step through PV based on mouse coordinates (called on mouse move)
5478   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5479
5480   // we must somehow check if right button is still down (might be released off board!)
5481   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5482   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5483   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5484   if(!step) return;
5485   lastX = x; lastY = y;
5486
5487   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5488   if(endPV < 0) return;
5489   if(y < margin) step = 1; else
5490   if(y > h - margin) step = -1;
5491   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5492   currentMove += step;
5493   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5494   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5495                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5496   DrawPosition(FALSE, boards[currentMove]);
5497 }
5498
5499
5500 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5501 // All positions will have equal probability, but the current method will not provide a unique
5502 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5503 #define DARK 1
5504 #define LITE 2
5505 #define ANY 3
5506
5507 int squaresLeft[4];
5508 int piecesLeft[(int)BlackPawn];
5509 int seed, nrOfShuffles;
5510
5511 void GetPositionNumber()
5512 {       // sets global variable seed
5513         int i;
5514
5515         seed = appData.defaultFrcPosition;
5516         if(seed < 0) { // randomize based on time for negative FRC position numbers
5517                 for(i=0; i<50; i++) seed += random();
5518                 seed = random() ^ random() >> 8 ^ random() << 8;
5519                 if(seed<0) seed = -seed;
5520         }
5521 }
5522
5523 int put(Board board, int pieceType, int rank, int n, int shade)
5524 // put the piece on the (n-1)-th empty squares of the given shade
5525 {
5526         int i;
5527
5528         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5529                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5530                         board[rank][i] = (ChessSquare) pieceType;
5531                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5532                         squaresLeft[ANY]--;
5533                         piecesLeft[pieceType]--;
5534                         return i;
5535                 }
5536         }
5537         return -1;
5538 }
5539
5540
5541 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5542 // calculate where the next piece goes, (any empty square), and put it there
5543 {
5544         int i;
5545
5546         i = seed % squaresLeft[shade];
5547         nrOfShuffles *= squaresLeft[shade];
5548         seed /= squaresLeft[shade];
5549         put(board, pieceType, rank, i, shade);
5550 }
5551
5552 void AddTwoPieces(Board board, int pieceType, int rank)
5553 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5554 {
5555         int i, n=squaresLeft[ANY], j=n-1, k;
5556
5557         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5558         i = seed % k;  // pick one
5559         nrOfShuffles *= k;
5560         seed /= k;
5561         while(i >= j) i -= j--;
5562         j = n - 1 - j; i += j;
5563         put(board, pieceType, rank, j, ANY);
5564         put(board, pieceType, rank, i, ANY);
5565 }
5566
5567 void SetUpShuffle(Board board, int number)
5568 {
5569         int i, p, first=1;
5570
5571         GetPositionNumber(); nrOfShuffles = 1;
5572
5573         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5574         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5575         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5576
5577         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5578
5579         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5580             p = (int) board[0][i];
5581             if(p < (int) BlackPawn) piecesLeft[p] ++;
5582             board[0][i] = EmptySquare;
5583         }
5584
5585         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5586             // shuffles restricted to allow normal castling put KRR first
5587             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5588                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5589             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5590                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5591             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5592                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5593             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5594                 put(board, WhiteRook, 0, 0, ANY);
5595             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5596         }
5597
5598         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5599             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5600             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5601                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5602                 while(piecesLeft[p] >= 2) {
5603                     AddOnePiece(board, p, 0, LITE);
5604                     AddOnePiece(board, p, 0, DARK);
5605                 }
5606                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5607             }
5608
5609         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5610             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5611             // but we leave King and Rooks for last, to possibly obey FRC restriction
5612             if(p == (int)WhiteRook) continue;
5613             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5614             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5615         }
5616
5617         // now everything is placed, except perhaps King (Unicorn) and Rooks
5618
5619         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5620             // Last King gets castling rights
5621             while(piecesLeft[(int)WhiteUnicorn]) {
5622                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5623                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5624             }
5625
5626             while(piecesLeft[(int)WhiteKing]) {
5627                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5628                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5629             }
5630
5631
5632         } else {
5633             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5634             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5635         }
5636
5637         // Only Rooks can be left; simply place them all
5638         while(piecesLeft[(int)WhiteRook]) {
5639                 i = put(board, WhiteRook, 0, 0, ANY);
5640                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5641                         if(first) {
5642                                 first=0;
5643                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5644                         }
5645                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5646                 }
5647         }
5648         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5649             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5650         }
5651
5652         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5653 }
5654
5655 int SetCharTable( char *table, const char * map )
5656 /* [HGM] moved here from winboard.c because of its general usefulness */
5657 /*       Basically a safe strcpy that uses the last character as King */
5658 {
5659     int result = FALSE; int NrPieces;
5660
5661     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5662                     && NrPieces >= 12 && !(NrPieces&1)) {
5663         int i; /* [HGM] Accept even length from 12 to 34 */
5664
5665         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5666         for( i=0; i<NrPieces/2-1; i++ ) {
5667             table[i] = map[i];
5668             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5669         }
5670         table[(int) WhiteKing]  = map[NrPieces/2-1];
5671         table[(int) BlackKing]  = map[NrPieces-1];
5672
5673         result = TRUE;
5674     }
5675
5676     return result;
5677 }
5678
5679 void Prelude(Board board)
5680 {       // [HGM] superchess: random selection of exo-pieces
5681         int i, j, k; ChessSquare p;
5682         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5683
5684         GetPositionNumber(); // use FRC position number
5685
5686         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5687             SetCharTable(pieceToChar, appData.pieceToCharTable);
5688             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5689                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5690         }
5691
5692         j = seed%4;                 seed /= 4;
5693         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5694         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5695         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5696         j = seed%3 + (seed%3 >= j); seed /= 3;
5697         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5700         j = seed%3;                 seed /= 3;
5701         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%2 + (seed%2 >= j); seed /= 2;
5705         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5709         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5710         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5711         put(board, exoPieces[0],    0, 0, ANY);
5712         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5713 }
5714
5715 void
5716 InitPosition(redraw)
5717      int redraw;
5718 {
5719     ChessSquare (* pieces)[BOARD_FILES];
5720     int i, j, pawnRow, overrule,
5721     oldx = gameInfo.boardWidth,
5722     oldy = gameInfo.boardHeight,
5723     oldh = gameInfo.holdingsWidth;
5724     static int oldv;
5725
5726     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5727
5728     /* [AS] Initialize pv info list [HGM] and game status */
5729     {
5730         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5731             pvInfoList[i].depth = 0;
5732             boards[i][EP_STATUS] = EP_NONE;
5733             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5734         }
5735
5736         initialRulePlies = 0; /* 50-move counter start */
5737
5738         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5739         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5740     }
5741
5742
5743     /* [HGM] logic here is completely changed. In stead of full positions */
5744     /* the initialized data only consist of the two backranks. The switch */
5745     /* selects which one we will use, which is than copied to the Board   */
5746     /* initialPosition, which for the rest is initialized by Pawns and    */
5747     /* empty squares. This initial position is then copied to boards[0],  */
5748     /* possibly after shuffling, so that it remains available.            */
5749
5750     gameInfo.holdingsWidth = 0; /* default board sizes */
5751     gameInfo.boardWidth    = 8;
5752     gameInfo.boardHeight   = 8;
5753     gameInfo.holdingsSize  = 0;
5754     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5755     for(i=0; i<BOARD_FILES-2; i++)
5756       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5757     initialPosition[EP_STATUS] = EP_NONE;
5758     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5759     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5760          SetCharTable(pieceNickName, appData.pieceNickNames);
5761     else SetCharTable(pieceNickName, "............");
5762     pieces = FIDEArray;
5763
5764     switch (gameInfo.variant) {
5765     case VariantFischeRandom:
5766       shuffleOpenings = TRUE;
5767     default:
5768       break;
5769     case VariantShatranj:
5770       pieces = ShatranjArray;
5771       nrCastlingRights = 0;
5772       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5773       break;
5774     case VariantMakruk:
5775       pieces = makrukArray;
5776       nrCastlingRights = 0;
5777       startedFromSetupPosition = TRUE;
5778       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5779       break;
5780     case VariantTwoKings:
5781       pieces = twoKingsArray;
5782       break;
5783     case VariantGrand:
5784       pieces = GrandArray;
5785       nrCastlingRights = 0;
5786       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5787       gameInfo.boardWidth = 10;
5788       gameInfo.boardHeight = 10;
5789       gameInfo.holdingsSize = 7;
5790       break;
5791     case VariantCapaRandom:
5792       shuffleOpenings = TRUE;
5793     case VariantCapablanca:
5794       pieces = CapablancaArray;
5795       gameInfo.boardWidth = 10;
5796       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5797       break;
5798     case VariantGothic:
5799       pieces = GothicArray;
5800       gameInfo.boardWidth = 10;
5801       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5802       break;
5803     case VariantSChess:
5804       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5805       gameInfo.holdingsSize = 7;
5806       break;
5807     case VariantJanus:
5808       pieces = JanusArray;
5809       gameInfo.boardWidth = 10;
5810       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5811       nrCastlingRights = 6;
5812         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5813         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5814         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5815         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5816         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5817         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5818       break;
5819     case VariantFalcon:
5820       pieces = FalconArray;
5821       gameInfo.boardWidth = 10;
5822       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5823       break;
5824     case VariantXiangqi:
5825       pieces = XiangqiArray;
5826       gameInfo.boardWidth  = 9;
5827       gameInfo.boardHeight = 10;
5828       nrCastlingRights = 0;
5829       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5830       break;
5831     case VariantShogi:
5832       pieces = ShogiArray;
5833       gameInfo.boardWidth  = 9;
5834       gameInfo.boardHeight = 9;
5835       gameInfo.holdingsSize = 7;
5836       nrCastlingRights = 0;
5837       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5838       break;
5839     case VariantCourier:
5840       pieces = CourierArray;
5841       gameInfo.boardWidth  = 12;
5842       nrCastlingRights = 0;
5843       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5844       break;
5845     case VariantKnightmate:
5846       pieces = KnightmateArray;
5847       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5848       break;
5849     case VariantSpartan:
5850       pieces = SpartanArray;
5851       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5852       break;
5853     case VariantFairy:
5854       pieces = fairyArray;
5855       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5856       break;
5857     case VariantGreat:
5858       pieces = GreatArray;
5859       gameInfo.boardWidth = 10;
5860       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5861       gameInfo.holdingsSize = 8;
5862       break;
5863     case VariantSuper:
5864       pieces = FIDEArray;
5865       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5866       gameInfo.holdingsSize = 8;
5867       startedFromSetupPosition = TRUE;
5868       break;
5869     case VariantCrazyhouse:
5870     case VariantBughouse:
5871       pieces = FIDEArray;
5872       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5873       gameInfo.holdingsSize = 5;
5874       break;
5875     case VariantWildCastle:
5876       pieces = FIDEArray;
5877       /* !!?shuffle with kings guaranteed to be on d or e file */
5878       shuffleOpenings = 1;
5879       break;
5880     case VariantNoCastle:
5881       pieces = FIDEArray;
5882       nrCastlingRights = 0;
5883       /* !!?unconstrained back-rank shuffle */
5884       shuffleOpenings = 1;
5885       break;
5886     }
5887
5888     overrule = 0;
5889     if(appData.NrFiles >= 0) {
5890         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5891         gameInfo.boardWidth = appData.NrFiles;
5892     }
5893     if(appData.NrRanks >= 0) {
5894         gameInfo.boardHeight = appData.NrRanks;
5895     }
5896     if(appData.holdingsSize >= 0) {
5897         i = appData.holdingsSize;
5898         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5899         gameInfo.holdingsSize = i;
5900     }
5901     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5902     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5903         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5904
5905     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5906     if(pawnRow < 1) pawnRow = 1;
5907     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5908
5909     /* User pieceToChar list overrules defaults */
5910     if(appData.pieceToCharTable != NULL)
5911         SetCharTable(pieceToChar, appData.pieceToCharTable);
5912
5913     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5914
5915         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5916             s = (ChessSquare) 0; /* account holding counts in guard band */
5917         for( i=0; i<BOARD_HEIGHT; i++ )
5918             initialPosition[i][j] = s;
5919
5920         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5921         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5922         initialPosition[pawnRow][j] = WhitePawn;
5923         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5924         if(gameInfo.variant == VariantXiangqi) {
5925             if(j&1) {
5926                 initialPosition[pawnRow][j] =
5927                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5928                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5929                    initialPosition[2][j] = WhiteCannon;
5930                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5931                 }
5932             }
5933         }
5934         if(gameInfo.variant == VariantGrand) {
5935             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5936                initialPosition[0][j] = WhiteRook;
5937                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5938             }
5939         }
5940         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5941     }
5942     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5943
5944             j=BOARD_LEFT+1;
5945             initialPosition[1][j] = WhiteBishop;
5946             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5947             j=BOARD_RGHT-2;
5948             initialPosition[1][j] = WhiteRook;
5949             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5950     }
5951
5952     if( nrCastlingRights == -1) {
5953         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5954         /*       This sets default castling rights from none to normal corners   */
5955         /* Variants with other castling rights must set them themselves above    */
5956         nrCastlingRights = 6;
5957
5958         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5959         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5960         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5961         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5962         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5963         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5964      }
5965
5966      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5967      if(gameInfo.variant == VariantGreat) { // promotion commoners
5968         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5969         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5970         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5971         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5972      }
5973      if( gameInfo.variant == VariantSChess ) {
5974       initialPosition[1][0] = BlackMarshall;
5975       initialPosition[2][0] = BlackAngel;
5976       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5977       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5978       initialPosition[1][1] = initialPosition[2][1] = 
5979       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5980      }
5981   if (appData.debugMode) {
5982     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5983   }
5984     if(shuffleOpenings) {
5985         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5986         startedFromSetupPosition = TRUE;
5987     }
5988     if(startedFromPositionFile) {
5989       /* [HGM] loadPos: use PositionFile for every new game */
5990       CopyBoard(initialPosition, filePosition);
5991       for(i=0; i<nrCastlingRights; i++)
5992           initialRights[i] = filePosition[CASTLING][i];
5993       startedFromSetupPosition = TRUE;
5994     }
5995
5996     CopyBoard(boards[0], initialPosition);
5997
5998     if(oldx != gameInfo.boardWidth ||
5999        oldy != gameInfo.boardHeight ||
6000        oldv != gameInfo.variant ||
6001        oldh != gameInfo.holdingsWidth
6002                                          )
6003             InitDrawingSizes(-2 ,0);
6004
6005     oldv = gameInfo.variant;
6006     if (redraw)
6007       DrawPosition(TRUE, boards[currentMove]);
6008 }
6009
6010 void
6011 SendBoard(cps, moveNum)
6012      ChessProgramState *cps;
6013      int moveNum;
6014 {
6015     char message[MSG_SIZ];
6016
6017     if (cps->useSetboard) {
6018       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6019       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6020       SendToProgram(message, cps);
6021       free(fen);
6022
6023     } else {
6024       ChessSquare *bp;
6025       int i, j;
6026       /* Kludge to set black to move, avoiding the troublesome and now
6027        * deprecated "black" command.
6028        */
6029       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6030         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6031
6032       SendToProgram("edit\n", cps);
6033       SendToProgram("#\n", cps);
6034       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6035         bp = &boards[moveNum][i][BOARD_LEFT];
6036         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6037           if ((int) *bp < (int) BlackPawn) {
6038             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6039                     AAA + j, ONE + i);
6040             if(message[0] == '+' || message[0] == '~') {
6041               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6042                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6043                         AAA + j, ONE + i);
6044             }
6045             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6046                 message[1] = BOARD_RGHT   - 1 - j + '1';
6047                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6048             }
6049             SendToProgram(message, cps);
6050           }
6051         }
6052       }
6053
6054       SendToProgram("c\n", cps);
6055       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6056         bp = &boards[moveNum][i][BOARD_LEFT];
6057         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6058           if (((int) *bp != (int) EmptySquare)
6059               && ((int) *bp >= (int) BlackPawn)) {
6060             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6061                     AAA + j, ONE + i);
6062             if(message[0] == '+' || message[0] == '~') {
6063               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6064                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6065                         AAA + j, ONE + i);
6066             }
6067             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6068                 message[1] = BOARD_RGHT   - 1 - j + '1';
6069                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6070             }
6071             SendToProgram(message, cps);
6072           }
6073         }
6074       }
6075
6076       SendToProgram(".\n", cps);
6077     }
6078     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6079 }
6080
6081 ChessSquare
6082 DefaultPromoChoice(int white)
6083 {
6084     ChessSquare result;
6085     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6086         result = WhiteFerz; // no choice
6087     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6088         result= WhiteKing; // in Suicide Q is the last thing we want
6089     else if(gameInfo.variant == VariantSpartan)
6090         result = white ? WhiteQueen : WhiteAngel;
6091     else result = WhiteQueen;
6092     if(!white) result = WHITE_TO_BLACK result;
6093     return result;
6094 }
6095
6096 static int autoQueen; // [HGM] oneclick
6097
6098 int
6099 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6100 {
6101     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6102     /* [HGM] add Shogi promotions */
6103     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6104     ChessSquare piece;
6105     ChessMove moveType;
6106     Boolean premove;
6107
6108     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6109     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6110
6111     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6112       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6113         return FALSE;
6114
6115     piece = boards[currentMove][fromY][fromX];
6116     if(gameInfo.variant == VariantShogi) {
6117         promotionZoneSize = BOARD_HEIGHT/3;
6118         highestPromotingPiece = (int)WhiteFerz;
6119     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6120         promotionZoneSize = 3;
6121     }
6122
6123     // Treat Lance as Pawn when it is not representing Amazon
6124     if(gameInfo.variant != VariantSuper) {
6125         if(piece == WhiteLance) piece = WhitePawn; else
6126         if(piece == BlackLance) piece = BlackPawn;
6127     }
6128
6129     // next weed out all moves that do not touch the promotion zone at all
6130     if((int)piece >= BlackPawn) {
6131         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6132              return FALSE;
6133         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6134     } else {
6135         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6136            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6137     }
6138
6139     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6140
6141     // weed out mandatory Shogi promotions
6142     if(gameInfo.variant == VariantShogi) {
6143         if(piece >= BlackPawn) {
6144             if(toY == 0 && piece == BlackPawn ||
6145                toY == 0 && piece == BlackQueen ||
6146                toY <= 1 && piece == BlackKnight) {
6147                 *promoChoice = '+';
6148                 return FALSE;
6149             }
6150         } else {
6151             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6152                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6153                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6154                 *promoChoice = '+';
6155                 return FALSE;
6156             }
6157         }
6158     }
6159
6160     // weed out obviously illegal Pawn moves
6161     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6162         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6163         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6164         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6165         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6166         // note we are not allowed to test for valid (non-)capture, due to premove
6167     }
6168
6169     // we either have a choice what to promote to, or (in Shogi) whether to promote
6170     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6171         *promoChoice = PieceToChar(BlackFerz);  // no choice
6172         return FALSE;
6173     }
6174     // no sense asking what we must promote to if it is going to explode...
6175     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6176         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6177         return FALSE;
6178     }
6179     // give caller the default choice even if we will not make it
6180     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6181     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6182     if(        sweepSelect && gameInfo.variant != VariantGreat
6183                            && gameInfo.variant != VariantGrand
6184                            && gameInfo.variant != VariantSuper) return FALSE;
6185     if(autoQueen) return FALSE; // predetermined
6186
6187     // suppress promotion popup on illegal moves that are not premoves
6188     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6189               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6190     if(appData.testLegality && !premove) {
6191         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6192                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6193         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6194             return FALSE;
6195     }
6196
6197     return TRUE;
6198 }
6199
6200 int
6201 InPalace(row, column)
6202      int row, column;
6203 {   /* [HGM] for Xiangqi */
6204     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6205          column < (BOARD_WIDTH + 4)/2 &&
6206          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6207     return FALSE;
6208 }
6209
6210 int
6211 PieceForSquare (x, y)
6212      int x;
6213      int y;
6214 {
6215   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6216      return -1;
6217   else
6218      return boards[currentMove][y][x];
6219 }
6220
6221 int
6222 OKToStartUserMove(x, y)
6223      int x, y;
6224 {
6225     ChessSquare from_piece;
6226     int white_piece;
6227
6228     if (matchMode) return FALSE;
6229     if (gameMode == EditPosition) return TRUE;
6230
6231     if (x >= 0 && y >= 0)
6232       from_piece = boards[currentMove][y][x];
6233     else
6234       from_piece = EmptySquare;
6235
6236     if (from_piece == EmptySquare) return FALSE;
6237
6238     white_piece = (int)from_piece >= (int)WhitePawn &&
6239       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6240
6241     switch (gameMode) {
6242       case AnalyzeFile:
6243       case TwoMachinesPlay:
6244       case EndOfGame:
6245         return FALSE;
6246
6247       case IcsObserving:
6248       case IcsIdle:
6249         return FALSE;
6250
6251       case MachinePlaysWhite:
6252       case IcsPlayingBlack:
6253         if (appData.zippyPlay) return FALSE;
6254         if (white_piece) {
6255             DisplayMoveError(_("You are playing Black"));
6256             return FALSE;
6257         }
6258         break;
6259
6260       case MachinePlaysBlack:
6261       case IcsPlayingWhite:
6262         if (appData.zippyPlay) return FALSE;
6263         if (!white_piece) {
6264             DisplayMoveError(_("You are playing White"));
6265             return FALSE;
6266         }
6267         break;
6268
6269       case PlayFromGameFile:
6270             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6271       case EditGame:
6272         if (!white_piece && WhiteOnMove(currentMove)) {
6273             DisplayMoveError(_("It is White's turn"));
6274             return FALSE;
6275         }
6276         if (white_piece && !WhiteOnMove(currentMove)) {
6277             DisplayMoveError(_("It is Black's turn"));
6278             return FALSE;
6279         }
6280         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6281             /* Editing correspondence game history */
6282             /* Could disallow this or prompt for confirmation */
6283             cmailOldMove = -1;
6284         }
6285         break;
6286
6287       case BeginningOfGame:
6288         if (appData.icsActive) return FALSE;
6289         if (!appData.noChessProgram) {
6290             if (!white_piece) {
6291                 DisplayMoveError(_("You are playing White"));
6292                 return FALSE;
6293             }
6294         }
6295         break;
6296
6297       case Training:
6298         if (!white_piece && WhiteOnMove(currentMove)) {
6299             DisplayMoveError(_("It is White's turn"));
6300             return FALSE;
6301         }
6302         if (white_piece && !WhiteOnMove(currentMove)) {
6303             DisplayMoveError(_("It is Black's turn"));
6304             return FALSE;
6305         }
6306         break;
6307
6308       default:
6309       case IcsExamining:
6310         break;
6311     }
6312     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6313         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6314         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6315         && gameMode != AnalyzeFile && gameMode != Training) {
6316         DisplayMoveError(_("Displayed position is not current"));
6317         return FALSE;
6318     }
6319     return TRUE;
6320 }
6321
6322 Boolean
6323 OnlyMove(int *x, int *y, Boolean captures) {
6324     DisambiguateClosure cl;
6325     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6326     switch(gameMode) {
6327       case MachinePlaysBlack:
6328       case IcsPlayingWhite:
6329       case BeginningOfGame:
6330         if(!WhiteOnMove(currentMove)) return FALSE;
6331         break;
6332       case MachinePlaysWhite:
6333       case IcsPlayingBlack:
6334         if(WhiteOnMove(currentMove)) return FALSE;
6335         break;
6336       case EditGame:
6337         break;
6338       default:
6339         return FALSE;
6340     }
6341     cl.pieceIn = EmptySquare;
6342     cl.rfIn = *y;
6343     cl.ffIn = *x;
6344     cl.rtIn = -1;
6345     cl.ftIn = -1;
6346     cl.promoCharIn = NULLCHAR;
6347     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6348     if( cl.kind == NormalMove ||
6349         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6350         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6351         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6352       fromX = cl.ff;
6353       fromY = cl.rf;
6354       *x = cl.ft;
6355       *y = cl.rt;
6356       return TRUE;
6357     }
6358     if(cl.kind != ImpossibleMove) return FALSE;
6359     cl.pieceIn = EmptySquare;
6360     cl.rfIn = -1;
6361     cl.ffIn = -1;
6362     cl.rtIn = *y;
6363     cl.ftIn = *x;
6364     cl.promoCharIn = NULLCHAR;
6365     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6366     if( cl.kind == NormalMove ||
6367         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6368         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6369         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6370       fromX = cl.ff;
6371       fromY = cl.rf;
6372       *x = cl.ft;
6373       *y = cl.rt;
6374       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6375       return TRUE;
6376     }
6377     return FALSE;
6378 }
6379
6380 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6381 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6382 int lastLoadGameUseList = FALSE;
6383 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6384 ChessMove lastLoadGameStart = EndOfFile;
6385
6386 void
6387 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6388      int fromX, fromY, toX, toY;
6389      int promoChar;
6390 {
6391     ChessMove moveType;
6392     ChessSquare pdown, pup;
6393
6394     /* Check if the user is playing in turn.  This is complicated because we
6395        let the user "pick up" a piece before it is his turn.  So the piece he
6396        tried to pick up may have been captured by the time he puts it down!
6397        Therefore we use the color the user is supposed to be playing in this
6398        test, not the color of the piece that is currently on the starting
6399        square---except in EditGame mode, where the user is playing both
6400        sides; fortunately there the capture race can't happen.  (It can
6401        now happen in IcsExamining mode, but that's just too bad.  The user
6402        will get a somewhat confusing message in that case.)
6403        */
6404
6405     switch (gameMode) {
6406       case AnalyzeFile:
6407       case TwoMachinesPlay:
6408       case EndOfGame:
6409       case IcsObserving:
6410       case IcsIdle:
6411         /* We switched into a game mode where moves are not accepted,
6412            perhaps while the mouse button was down. */
6413         return;
6414
6415       case MachinePlaysWhite:
6416         /* User is moving for Black */
6417         if (WhiteOnMove(currentMove)) {
6418             DisplayMoveError(_("It is White's turn"));
6419             return;
6420         }
6421         break;
6422
6423       case MachinePlaysBlack:
6424         /* User is moving for White */
6425         if (!WhiteOnMove(currentMove)) {
6426             DisplayMoveError(_("It is Black's turn"));
6427             return;
6428         }
6429         break;
6430
6431       case PlayFromGameFile:
6432             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6433       case EditGame:
6434       case IcsExamining:
6435       case BeginningOfGame:
6436       case AnalyzeMode:
6437       case Training:
6438         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6439         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6440             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6441             /* User is moving for Black */
6442             if (WhiteOnMove(currentMove)) {
6443                 DisplayMoveError(_("It is White's turn"));
6444                 return;
6445             }
6446         } else {
6447             /* User is moving for White */
6448             if (!WhiteOnMove(currentMove)) {
6449                 DisplayMoveError(_("It is Black's turn"));
6450                 return;
6451             }
6452         }
6453         break;
6454
6455       case IcsPlayingBlack:
6456         /* User is moving for Black */
6457         if (WhiteOnMove(currentMove)) {
6458             if (!appData.premove) {
6459                 DisplayMoveError(_("It is White's turn"));
6460             } else if (toX >= 0 && toY >= 0) {
6461                 premoveToX = toX;
6462                 premoveToY = toY;
6463                 premoveFromX = fromX;
6464                 premoveFromY = fromY;
6465                 premovePromoChar = promoChar;
6466                 gotPremove = 1;
6467                 if (appData.debugMode)
6468                     fprintf(debugFP, "Got premove: fromX %d,"
6469                             "fromY %d, toX %d, toY %d\n",
6470                             fromX, fromY, toX, toY);
6471             }
6472             return;
6473         }
6474         break;
6475
6476       case IcsPlayingWhite:
6477         /* User is moving for White */
6478         if (!WhiteOnMove(currentMove)) {
6479             if (!appData.premove) {
6480                 DisplayMoveError(_("It is Black's turn"));
6481             } else if (toX >= 0 && toY >= 0) {
6482                 premoveToX = toX;
6483                 premoveToY = toY;
6484                 premoveFromX = fromX;
6485                 premoveFromY = fromY;
6486                 premovePromoChar = promoChar;
6487                 gotPremove = 1;
6488                 if (appData.debugMode)
6489                     fprintf(debugFP, "Got premove: fromX %d,"
6490                             "fromY %d, toX %d, toY %d\n",
6491                             fromX, fromY, toX, toY);
6492             }
6493             return;
6494         }
6495         break;
6496
6497       default:
6498         break;
6499
6500       case EditPosition:
6501         /* EditPosition, empty square, or different color piece;
6502            click-click move is possible */
6503         if (toX == -2 || toY == -2) {
6504             boards[0][fromY][fromX] = EmptySquare;
6505             DrawPosition(FALSE, boards[currentMove]);
6506             return;
6507         } else if (toX >= 0 && toY >= 0) {
6508             boards[0][toY][toX] = boards[0][fromY][fromX];
6509             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6510                 if(boards[0][fromY][0] != EmptySquare) {
6511                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6512                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6513                 }
6514             } else
6515             if(fromX == BOARD_RGHT+1) {
6516                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6517                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6518                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6519                 }
6520             } else
6521             boards[0][fromY][fromX] = EmptySquare;
6522             DrawPosition(FALSE, boards[currentMove]);
6523             return;
6524         }
6525         return;
6526     }
6527
6528     if(toX < 0 || toY < 0) return;
6529     pdown = boards[currentMove][fromY][fromX];
6530     pup = boards[currentMove][toY][toX];
6531
6532     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6533     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6534          if( pup != EmptySquare ) return;
6535          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6536            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6537                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6538            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6539            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6540            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6541            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6542          fromY = DROP_RANK;
6543     }
6544
6545     /* [HGM] always test for legality, to get promotion info */
6546     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6547                                          fromY, fromX, toY, toX, promoChar);
6548
6549     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6550
6551     /* [HGM] but possibly ignore an IllegalMove result */
6552     if (appData.testLegality) {
6553         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6554             DisplayMoveError(_("Illegal move"));
6555             return;
6556         }
6557     }
6558
6559     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6560 }
6561
6562 /* Common tail of UserMoveEvent and DropMenuEvent */
6563 int
6564 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6565      ChessMove moveType;
6566      int fromX, fromY, toX, toY;
6567      /*char*/int promoChar;
6568 {
6569     char *bookHit = 0;
6570
6571     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6572         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6573         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6574         if(WhiteOnMove(currentMove)) {
6575             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6576         } else {
6577             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6578         }
6579     }
6580
6581     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6582        move type in caller when we know the move is a legal promotion */
6583     if(moveType == NormalMove && promoChar)
6584         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6585
6586     /* [HGM] <popupFix> The following if has been moved here from
6587        UserMoveEvent(). Because it seemed to belong here (why not allow
6588        piece drops in training games?), and because it can only be
6589        performed after it is known to what we promote. */
6590     if (gameMode == Training) {
6591       /* compare the move played on the board to the next move in the
6592        * game. If they match, display the move and the opponent's response.
6593        * If they don't match, display an error message.
6594        */
6595       int saveAnimate;
6596       Board testBoard;
6597       CopyBoard(testBoard, boards[currentMove]);
6598       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6599
6600       if (CompareBoards(testBoard, boards[currentMove+1])) {
6601         ForwardInner(currentMove+1);
6602
6603         /* Autoplay the opponent's response.
6604          * if appData.animate was TRUE when Training mode was entered,
6605          * the response will be animated.
6606          */
6607         saveAnimate = appData.animate;
6608         appData.animate = animateTraining;
6609         ForwardInner(currentMove+1);
6610         appData.animate = saveAnimate;
6611
6612         /* check for the end of the game */
6613         if (currentMove >= forwardMostMove) {
6614           gameMode = PlayFromGameFile;
6615           ModeHighlight();
6616           SetTrainingModeOff();
6617           DisplayInformation(_("End of game"));
6618         }
6619       } else {
6620         DisplayError(_("Incorrect move"), 0);
6621       }
6622       return 1;
6623     }
6624
6625   /* Ok, now we know that the move is good, so we can kill
6626      the previous line in Analysis Mode */
6627   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6628                                 && currentMove < forwardMostMove) {
6629     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6630     else forwardMostMove = currentMove;
6631   }
6632
6633   /* If we need the chess program but it's dead, restart it */
6634   ResurrectChessProgram();
6635
6636   /* A user move restarts a paused game*/
6637   if (pausing)
6638     PauseEvent();
6639
6640   thinkOutput[0] = NULLCHAR;
6641
6642   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6643
6644   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6645     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6646     return 1;
6647   }
6648
6649   if (gameMode == BeginningOfGame) {
6650     if (appData.noChessProgram) {
6651       gameMode = EditGame;
6652       SetGameInfo();
6653     } else {
6654       char buf[MSG_SIZ];
6655       gameMode = MachinePlaysBlack;
6656       StartClocks();
6657       SetGameInfo();
6658       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6659       DisplayTitle(buf);
6660       if (first.sendName) {
6661         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6662         SendToProgram(buf, &first);
6663       }
6664       StartClocks();
6665     }
6666     ModeHighlight();
6667   }
6668
6669   /* Relay move to ICS or chess engine */
6670   if (appData.icsActive) {
6671     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6672         gameMode == IcsExamining) {
6673       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6674         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6675         SendToICS("draw ");
6676         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6677       }
6678       // also send plain move, in case ICS does not understand atomic claims
6679       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6680       ics_user_moved = 1;
6681     }
6682   } else {
6683     if (first.sendTime && (gameMode == BeginningOfGame ||
6684                            gameMode == MachinePlaysWhite ||
6685                            gameMode == MachinePlaysBlack)) {
6686       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6687     }
6688     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6689          // [HGM] book: if program might be playing, let it use book
6690         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6691         first.maybeThinking = TRUE;
6692     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6693         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6694         SendBoard(&first, currentMove+1);
6695     } else SendMoveToProgram(forwardMostMove-1, &first);
6696     if (currentMove == cmailOldMove + 1) {
6697       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6698     }
6699   }
6700
6701   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6702
6703   switch (gameMode) {
6704   case EditGame:
6705     if(appData.testLegality)
6706     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6707     case MT_NONE:
6708     case MT_CHECK:
6709       break;
6710     case MT_CHECKMATE:
6711     case MT_STAINMATE:
6712       if (WhiteOnMove(currentMove)) {
6713         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6714       } else {
6715         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6716       }
6717       break;
6718     case MT_STALEMATE:
6719       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6720       break;
6721     }
6722     break;
6723
6724   case MachinePlaysBlack:
6725   case MachinePlaysWhite:
6726     /* disable certain menu options while machine is thinking */
6727     SetMachineThinkingEnables();
6728     break;
6729
6730   default:
6731     break;
6732   }
6733
6734   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6735   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6736
6737   if(bookHit) { // [HGM] book: simulate book reply
6738         static char bookMove[MSG_SIZ]; // a bit generous?
6739
6740         programStats.nodes = programStats.depth = programStats.time =
6741         programStats.score = programStats.got_only_move = 0;
6742         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6743
6744         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6745         strcat(bookMove, bookHit);
6746         HandleMachineMove(bookMove, &first);
6747   }
6748   return 1;
6749 }
6750
6751 void
6752 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6753      Board board;
6754      int flags;
6755      ChessMove kind;
6756      int rf, ff, rt, ft;
6757      VOIDSTAR closure;
6758 {
6759     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6760     Markers *m = (Markers *) closure;
6761     if(rf == fromY && ff == fromX)
6762         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6763                          || kind == WhiteCapturesEnPassant
6764                          || kind == BlackCapturesEnPassant);
6765     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6766 }
6767
6768 void
6769 MarkTargetSquares(int clear)
6770 {
6771   int x, y;
6772   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6773      !appData.testLegality || gameMode == EditPosition) return;
6774   if(clear) {
6775     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6776   } else {
6777     int capt = 0;
6778     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6779     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6780       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6781       if(capt)
6782       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6783     }
6784   }
6785   DrawPosition(TRUE, NULL);
6786 }
6787
6788 int
6789 Explode(Board board, int fromX, int fromY, int toX, int toY)
6790 {
6791     if(gameInfo.variant == VariantAtomic &&
6792        (board[toY][toX] != EmptySquare ||                     // capture?
6793         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6794                          board[fromY][fromX] == BlackPawn   )
6795       )) {
6796         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6797         return TRUE;
6798     }
6799     return FALSE;
6800 }
6801
6802 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6803
6804 int CanPromote(ChessSquare piece, int y)
6805 {
6806         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6807         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6808         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6809            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6810            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6811                                                   gameInfo.variant == VariantMakruk) return FALSE;
6812         return (piece == BlackPawn && y == 1 ||
6813                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6814                 piece == BlackLance && y == 1 ||
6815                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6816 }
6817
6818 void LeftClick(ClickType clickType, int xPix, int yPix)
6819 {
6820     int x, y;
6821     Boolean saveAnimate;
6822     static int second = 0, promotionChoice = 0, clearFlag = 0;
6823     char promoChoice = NULLCHAR;
6824     ChessSquare piece;
6825
6826     if(appData.seekGraph && appData.icsActive && loggedOn &&
6827         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6828         SeekGraphClick(clickType, xPix, yPix, 0);
6829         return;
6830     }
6831
6832     if (clickType == Press) ErrorPopDown();
6833
6834     x = EventToSquare(xPix, BOARD_WIDTH);
6835     y = EventToSquare(yPix, BOARD_HEIGHT);
6836     if (!flipView && y >= 0) {
6837         y = BOARD_HEIGHT - 1 - y;
6838     }
6839     if (flipView && x >= 0) {
6840         x = BOARD_WIDTH - 1 - x;
6841     }
6842
6843     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6844         defaultPromoChoice = promoSweep;
6845         promoSweep = EmptySquare;   // terminate sweep
6846         promoDefaultAltered = TRUE;
6847         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6848     }
6849
6850     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6851         if(clickType == Release) return; // ignore upclick of click-click destination
6852         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6853         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6854         if(gameInfo.holdingsWidth &&
6855                 (WhiteOnMove(currentMove)
6856                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6857                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6858             // click in right holdings, for determining promotion piece
6859             ChessSquare p = boards[currentMove][y][x];
6860             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6861             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6862             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6863                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6864                 fromX = fromY = -1;
6865                 return;
6866             }
6867         }
6868         DrawPosition(FALSE, boards[currentMove]);
6869         return;
6870     }
6871
6872     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6873     if(clickType == Press
6874             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6875               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6876               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6877         return;
6878
6879     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6880         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6881
6882     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6883         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6884                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6885         defaultPromoChoice = DefaultPromoChoice(side);
6886     }
6887
6888     autoQueen = appData.alwaysPromoteToQueen;
6889
6890     if (fromX == -1) {
6891       int originalY = y;
6892       gatingPiece = EmptySquare;
6893       if (clickType != Press) {
6894         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6895             DragPieceEnd(xPix, yPix); dragging = 0;
6896             DrawPosition(FALSE, NULL);
6897         }
6898         return;
6899       }
6900       fromX = x; fromY = y; toX = toY = -1;
6901       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6902          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6903          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6904             /* First square */
6905             if (OKToStartUserMove(fromX, fromY)) {
6906                 second = 0;
6907                 MarkTargetSquares(0);
6908                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6909                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6910                     promoSweep = defaultPromoChoice;
6911                     selectFlag = 0; lastX = xPix; lastY = yPix;
6912                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6913                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6914                 }
6915                 if (appData.highlightDragging) {
6916                     SetHighlights(fromX, fromY, -1, -1);
6917                 }
6918             } else fromX = fromY = -1;
6919             return;
6920         }
6921     }
6922
6923     /* fromX != -1 */
6924     if (clickType == Press && gameMode != EditPosition) {
6925         ChessSquare fromP;
6926         ChessSquare toP;
6927         int frc;
6928
6929         // ignore off-board to clicks
6930         if(y < 0 || x < 0) return;
6931
6932         /* Check if clicking again on the same color piece */
6933         fromP = boards[currentMove][fromY][fromX];
6934         toP = boards[currentMove][y][x];
6935         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6936         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6937              WhitePawn <= toP && toP <= WhiteKing &&
6938              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6939              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6940             (BlackPawn <= fromP && fromP <= BlackKing &&
6941              BlackPawn <= toP && toP <= BlackKing &&
6942              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6943              !(fromP == BlackKing && toP == BlackRook && frc))) {
6944             /* Clicked again on same color piece -- changed his mind */
6945             second = (x == fromX && y == fromY);
6946             promoDefaultAltered = FALSE;
6947             MarkTargetSquares(1);
6948            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6949             if (appData.highlightDragging) {
6950                 SetHighlights(x, y, -1, -1);
6951             } else {
6952                 ClearHighlights();
6953             }
6954             if (OKToStartUserMove(x, y)) {
6955                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6956                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6957                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6958                  gatingPiece = boards[currentMove][fromY][fromX];
6959                 else gatingPiece = EmptySquare;
6960                 fromX = x;
6961                 fromY = y; dragging = 1;
6962                 MarkTargetSquares(0);
6963                 DragPieceBegin(xPix, yPix, FALSE);
6964                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6965                     promoSweep = defaultPromoChoice;
6966                     selectFlag = 0; lastX = xPix; lastY = yPix;
6967                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6968                 }
6969             }
6970            }
6971            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6972            second = FALSE; 
6973         }
6974         // ignore clicks on holdings
6975         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6976     }
6977
6978     if (clickType == Release && x == fromX && y == fromY) {
6979         DragPieceEnd(xPix, yPix); dragging = 0;
6980         if(clearFlag) {
6981             // a deferred attempt to click-click move an empty square on top of a piece
6982             boards[currentMove][y][x] = EmptySquare;
6983             ClearHighlights();
6984             DrawPosition(FALSE, boards[currentMove]);
6985             fromX = fromY = -1; clearFlag = 0;
6986             return;
6987         }
6988         if (appData.animateDragging) {
6989             /* Undo animation damage if any */
6990             DrawPosition(FALSE, NULL);
6991         }
6992         if (second) {
6993             /* Second up/down in same square; just abort move */
6994             second = 0;
6995             fromX = fromY = -1;
6996             gatingPiece = EmptySquare;
6997             ClearHighlights();
6998             gotPremove = 0;
6999             ClearPremoveHighlights();
7000         } else {
7001             /* First upclick in same square; start click-click mode */
7002             SetHighlights(x, y, -1, -1);
7003         }
7004         return;
7005     }
7006
7007     clearFlag = 0;
7008
7009     /* we now have a different from- and (possibly off-board) to-square */
7010     /* Completed move */
7011     toX = x;
7012     toY = y;
7013     saveAnimate = appData.animate;
7014     MarkTargetSquares(1);
7015     if (clickType == Press) {
7016         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7017             // must be Edit Position mode with empty-square selected
7018             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7019             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7020             return;
7021         }
7022         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7023             ChessSquare piece = boards[currentMove][fromY][fromX];
7024             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7025             promoSweep = defaultPromoChoice;
7026             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7027             selectFlag = 0; lastX = xPix; lastY = yPix;
7028             Sweep(0); // Pawn that is going to promote: preview promotion piece
7029             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7030             DrawPosition(FALSE, boards[currentMove]);
7031             return;
7032         }
7033         /* Finish clickclick move */
7034         if (appData.animate || appData.highlightLastMove) {
7035             SetHighlights(fromX, fromY, toX, toY);
7036         } else {
7037             ClearHighlights();
7038         }
7039     } else {
7040         /* Finish drag move */
7041         if (appData.highlightLastMove) {
7042             SetHighlights(fromX, fromY, toX, toY);
7043         } else {
7044             ClearHighlights();
7045         }
7046         DragPieceEnd(xPix, yPix); dragging = 0;
7047         /* Don't animate move and drag both */
7048         appData.animate = FALSE;
7049     }
7050
7051     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7052     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7053         ChessSquare piece = boards[currentMove][fromY][fromX];
7054         if(gameMode == EditPosition && piece != EmptySquare &&
7055            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7056             int n;
7057
7058             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7059                 n = PieceToNumber(piece - (int)BlackPawn);
7060                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7061                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7063             } else
7064             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7065                 n = PieceToNumber(piece);
7066                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7067                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7068                 boards[currentMove][n][BOARD_WIDTH-2]++;
7069             }
7070             boards[currentMove][fromY][fromX] = EmptySquare;
7071         }
7072         ClearHighlights();
7073         fromX = fromY = -1;
7074         DrawPosition(TRUE, boards[currentMove]);
7075         return;
7076     }
7077
7078     // off-board moves should not be highlighted
7079     if(x < 0 || y < 0) ClearHighlights();
7080
7081     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7082
7083     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7084         SetHighlights(fromX, fromY, toX, toY);
7085         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7086             // [HGM] super: promotion to captured piece selected from holdings
7087             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7088             promotionChoice = TRUE;
7089             // kludge follows to temporarily execute move on display, without promoting yet
7090             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7091             boards[currentMove][toY][toX] = p;
7092             DrawPosition(FALSE, boards[currentMove]);
7093             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7094             boards[currentMove][toY][toX] = q;
7095             DisplayMessage("Click in holdings to choose piece", "");
7096             return;
7097         }
7098         PromotionPopUp();
7099     } else {
7100         int oldMove = currentMove;
7101         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7102         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7103         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7104         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7105            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7106             DrawPosition(TRUE, boards[currentMove]);
7107         fromX = fromY = -1;
7108     }
7109     appData.animate = saveAnimate;
7110     if (appData.animate || appData.animateDragging) {
7111         /* Undo animation damage if needed */
7112         DrawPosition(FALSE, NULL);
7113     }
7114 }
7115
7116 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7117 {   // front-end-free part taken out of PieceMenuPopup
7118     int whichMenu; int xSqr, ySqr;
7119
7120     if(seekGraphUp) { // [HGM] seekgraph
7121         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7122         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7123         return -2;
7124     }
7125
7126     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7127          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7128         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7129         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7130         if(action == Press)   {
7131             originalFlip = flipView;
7132             flipView = !flipView; // temporarily flip board to see game from partners perspective
7133             DrawPosition(TRUE, partnerBoard);
7134             DisplayMessage(partnerStatus, "");
7135             partnerUp = TRUE;
7136         } else if(action == Release) {
7137             flipView = originalFlip;
7138             DrawPosition(TRUE, boards[currentMove]);
7139             partnerUp = FALSE;
7140         }
7141         return -2;
7142     }
7143
7144     xSqr = EventToSquare(x, BOARD_WIDTH);
7145     ySqr = EventToSquare(y, BOARD_HEIGHT);
7146     if (action == Release) {
7147         if(pieceSweep != EmptySquare) {
7148             EditPositionMenuEvent(pieceSweep, toX, toY);
7149             pieceSweep = EmptySquare;
7150         } else UnLoadPV(); // [HGM] pv
7151     }
7152     if (action != Press) return -2; // return code to be ignored
7153     switch (gameMode) {
7154       case IcsExamining:
7155         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7156       case EditPosition:
7157         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7158         if (xSqr < 0 || ySqr < 0) return -1;
7159         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7160         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7161         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7162         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7163         NextPiece(0);
7164         return 2; // grab
7165       case IcsObserving:
7166         if(!appData.icsEngineAnalyze) return -1;
7167       case IcsPlayingWhite:
7168       case IcsPlayingBlack:
7169         if(!appData.zippyPlay) goto noZip;
7170       case AnalyzeMode:
7171       case AnalyzeFile:
7172       case MachinePlaysWhite:
7173       case MachinePlaysBlack:
7174       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7175         if (!appData.dropMenu) {
7176           LoadPV(x, y);
7177           return 2; // flag front-end to grab mouse events
7178         }
7179         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7180            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7181       case EditGame:
7182       noZip:
7183         if (xSqr < 0 || ySqr < 0) return -1;
7184         if (!appData.dropMenu || appData.testLegality &&
7185             gameInfo.variant != VariantBughouse &&
7186             gameInfo.variant != VariantCrazyhouse) return -1;
7187         whichMenu = 1; // drop menu
7188         break;
7189       default:
7190         return -1;
7191     }
7192
7193     if (((*fromX = xSqr) < 0) ||
7194         ((*fromY = ySqr) < 0)) {
7195         *fromX = *fromY = -1;
7196         return -1;
7197     }
7198     if (flipView)
7199       *fromX = BOARD_WIDTH - 1 - *fromX;
7200     else
7201       *fromY = BOARD_HEIGHT - 1 - *fromY;
7202
7203     return whichMenu;
7204 }
7205
7206 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7207 {
7208 //    char * hint = lastHint;
7209     FrontEndProgramStats stats;
7210
7211     stats.which = cps == &first ? 0 : 1;
7212     stats.depth = cpstats->depth;
7213     stats.nodes = cpstats->nodes;
7214     stats.score = cpstats->score;
7215     stats.time = cpstats->time;
7216     stats.pv = cpstats->movelist;
7217     stats.hint = lastHint;
7218     stats.an_move_index = 0;
7219     stats.an_move_count = 0;
7220
7221     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7222         stats.hint = cpstats->move_name;
7223         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7224         stats.an_move_count = cpstats->nr_moves;
7225     }
7226
7227     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7228
7229     SetProgramStats( &stats );
7230 }
7231
7232 void
7233 ClearEngineOutputPane(int which)
7234 {
7235     static FrontEndProgramStats dummyStats;
7236     dummyStats.which = which;
7237     dummyStats.pv = "#";
7238     SetProgramStats( &dummyStats );
7239 }
7240
7241 #define MAXPLAYERS 500
7242
7243 char *
7244 TourneyStandings(int display)
7245 {
7246     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7247     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7248     char result, *p, *names[MAXPLAYERS];
7249
7250     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7251         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7252     names[0] = p = strdup(appData.participants);
7253     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7254
7255     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7256
7257     while(result = appData.results[nr]) {
7258         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7259         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7260         wScore = bScore = 0;
7261         switch(result) {
7262           case '+': wScore = 2; break;
7263           case '-': bScore = 2; break;
7264           case '=': wScore = bScore = 1; break;
7265           case ' ':
7266           case '*': return strdup("busy"); // tourney not finished
7267         }
7268         score[w] += wScore;
7269         score[b] += bScore;
7270         games[w]++;
7271         games[b]++;
7272         nr++;
7273     }
7274     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7275     for(w=0; w<nPlayers; w++) {
7276         bScore = -1;
7277         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7278         ranking[w] = b; points[w] = bScore; score[b] = -2;
7279     }
7280     p = malloc(nPlayers*34+1);
7281     for(w=0; w<nPlayers && w<display; w++)
7282         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7283     free(names[0]);
7284     return p;
7285 }
7286
7287 void
7288 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7289 {       // count all piece types
7290         int p, f, r;
7291         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7292         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7293         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7294                 p = board[r][f];
7295                 pCnt[p]++;
7296                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7297                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7298                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7299                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7300                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7301                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7302         }
7303 }
7304
7305 int
7306 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7307 {
7308         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7309         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7310
7311         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7312         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7313         if(myPawns == 2 && nMine == 3) // KPP
7314             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7315         if(myPawns == 1 && nMine == 2) // KP
7316             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7317         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7318             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7319         if(myPawns) return FALSE;
7320         if(pCnt[WhiteRook+side])
7321             return pCnt[BlackRook-side] ||
7322                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7323                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7324                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7325         if(pCnt[WhiteCannon+side]) {
7326             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7327             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7328         }
7329         if(pCnt[WhiteKnight+side])
7330             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7331         return FALSE;
7332 }
7333
7334 int
7335 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7336 {
7337         VariantClass v = gameInfo.variant;
7338
7339         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7340         if(v == VariantShatranj) return TRUE; // always winnable through baring
7341         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7342         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7343
7344         if(v == VariantXiangqi) {
7345                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7346
7347                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7348                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7349                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7350                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7351                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7352                 if(stale) // we have at least one last-rank P plus perhaps C
7353                     return majors // KPKX
7354                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7355                 else // KCA*E*
7356                     return pCnt[WhiteFerz+side] // KCAK
7357                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7358                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7359                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7360
7361         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7362                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7363
7364                 if(nMine == 1) return FALSE; // bare King
7365                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7366                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7367                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7368                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7369                 if(pCnt[WhiteKnight+side])
7370                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7371                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7372                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7373                 if(nBishops)
7374                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7375                 if(pCnt[WhiteAlfil+side])
7376                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7377                 if(pCnt[WhiteWazir+side])
7378                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7379         }
7380
7381         return TRUE;
7382 }
7383
7384 int
7385 CompareWithRights(Board b1, Board b2)
7386 {
7387     int rights = 0;
7388     if(!CompareBoards(b1, b2)) return FALSE;
7389     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7390     /* compare castling rights */
7391     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7392            rights++; /* King lost rights, while rook still had them */
7393     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7394         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7395            rights++; /* but at least one rook lost them */
7396     }
7397     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7398            rights++;
7399     if( b1[CASTLING][5] != NoRights ) {
7400         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7401            rights++;
7402     }
7403     return rights == 0;
7404 }
7405
7406 int
7407 Adjudicate(ChessProgramState *cps)
7408 {       // [HGM] some adjudications useful with buggy engines
7409         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7410         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7411         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7412         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7413         int k, count = 0; static int bare = 1;
7414         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7415         Boolean canAdjudicate = !appData.icsActive;
7416
7417         // most tests only when we understand the game, i.e. legality-checking on
7418             if( appData.testLegality )
7419             {   /* [HGM] Some more adjudications for obstinate engines */
7420                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7421                 static int moveCount = 6;
7422                 ChessMove result;
7423                 char *reason = NULL;
7424
7425                 /* Count what is on board. */
7426                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7427
7428                 /* Some material-based adjudications that have to be made before stalemate test */
7429                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7430                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7431                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7432                      if(canAdjudicate && appData.checkMates) {
7433                          if(engineOpponent)
7434                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7435                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7436                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7437                          return 1;
7438                      }
7439                 }
7440
7441                 /* Bare King in Shatranj (loses) or Losers (wins) */
7442                 if( nrW == 1 || nrB == 1) {
7443                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7444                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7445                      if(canAdjudicate && appData.checkMates) {
7446                          if(engineOpponent)
7447                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7448                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7449                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7450                          return 1;
7451                      }
7452                   } else
7453                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7454                   {    /* bare King */
7455                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7456                         if(canAdjudicate && appData.checkMates) {
7457                             /* but only adjudicate if adjudication enabled */
7458                             if(engineOpponent)
7459                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7460                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7461                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7462                             return 1;
7463                         }
7464                   }
7465                 } else bare = 1;
7466
7467
7468             // don't wait for engine to announce game end if we can judge ourselves
7469             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7470               case MT_CHECK:
7471                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7472                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7473                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7474                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7475                             checkCnt++;
7476                         if(checkCnt >= 2) {
7477                             reason = "Xboard adjudication: 3rd check";
7478                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7479                             break;
7480                         }
7481                     }
7482                 }
7483               case MT_NONE:
7484               default:
7485                 break;
7486               case MT_STALEMATE:
7487               case MT_STAINMATE:
7488                 reason = "Xboard adjudication: Stalemate";
7489                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7490                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7491                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7492                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7493                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7494                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7495                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7496                                                                         EP_CHECKMATE : EP_WINS);
7497                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7498                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7499                 }
7500                 break;
7501               case MT_CHECKMATE:
7502                 reason = "Xboard adjudication: Checkmate";
7503                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7504                 break;
7505             }
7506
7507                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7508                     case EP_STALEMATE:
7509                         result = GameIsDrawn; break;
7510                     case EP_CHECKMATE:
7511                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7512                     case EP_WINS:
7513                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7514                     default:
7515                         result = EndOfFile;
7516                 }
7517                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7518                     if(engineOpponent)
7519                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7520                     GameEnds( result, reason, GE_XBOARD );
7521                     return 1;
7522                 }
7523
7524                 /* Next absolutely insufficient mating material. */
7525                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7526                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7527                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7528
7529                      /* always flag draws, for judging claims */
7530                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7531
7532                      if(canAdjudicate && appData.materialDraws) {
7533                          /* but only adjudicate them if adjudication enabled */
7534                          if(engineOpponent) {
7535                            SendToProgram("force\n", engineOpponent); // suppress reply
7536                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7537                          }
7538                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7539                          return 1;
7540                      }
7541                 }
7542
7543                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7544                 if(gameInfo.variant == VariantXiangqi ?
7545                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7546                  : nrW + nrB == 4 &&
7547                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7548                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7549                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7550                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7551                    ) ) {
7552                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7553                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7554                           if(engineOpponent) {
7555                             SendToProgram("force\n", engineOpponent); // suppress reply
7556                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7557                           }
7558                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7559                           return 1;
7560                      }
7561                 } else moveCount = 6;
7562             }
7563         if (appData.debugMode) { int i;
7564             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7565                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7566                     appData.drawRepeats);
7567             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7568               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7569
7570         }
7571
7572         // Repetition draws and 50-move rule can be applied independently of legality testing
7573
7574                 /* Check for rep-draws */
7575                 count = 0;
7576                 for(k = forwardMostMove-2;
7577                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7578                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7579                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7580                     k-=2)
7581                 {   int rights=0;
7582                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7583                         /* compare castling rights */
7584                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7585                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7586                                 rights++; /* King lost rights, while rook still had them */
7587                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7588                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7589                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7590                                    rights++; /* but at least one rook lost them */
7591                         }
7592                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7593                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7594                                 rights++;
7595                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7596                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7597                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7598                                    rights++;
7599                         }
7600                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7601                             && appData.drawRepeats > 1) {
7602                              /* adjudicate after user-specified nr of repeats */
7603                              int result = GameIsDrawn;
7604                              char *details = "XBoard adjudication: repetition draw";
7605                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7606                                 // [HGM] xiangqi: check for forbidden perpetuals
7607                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7608                                 for(m=forwardMostMove; m>k; m-=2) {
7609                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7610                                         ourPerpetual = 0; // the current mover did not always check
7611                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7612                                         hisPerpetual = 0; // the opponent did not always check
7613                                 }
7614                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7615                                                                         ourPerpetual, hisPerpetual);
7616                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7617                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7618                                     details = "Xboard adjudication: perpetual checking";
7619                                 } else
7620                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7621                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7622                                 } else
7623                                 // Now check for perpetual chases
7624                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7625                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7626                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7627                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7628                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7629                                         details = "Xboard adjudication: perpetual chasing";
7630                                     } else
7631                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7632                                         break; // Abort repetition-checking loop.
7633                                 }
7634                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7635                              }
7636                              if(engineOpponent) {
7637                                SendToProgram("force\n", engineOpponent); // suppress reply
7638                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7639                              }
7640                              GameEnds( result, details, GE_XBOARD );
7641                              return 1;
7642                         }
7643                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7644                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7645                     }
7646                 }
7647
7648                 /* Now we test for 50-move draws. Determine ply count */
7649                 count = forwardMostMove;
7650                 /* look for last irreversble move */
7651                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7652                     count--;
7653                 /* if we hit starting position, add initial plies */
7654                 if( count == backwardMostMove )
7655                     count -= initialRulePlies;
7656                 count = forwardMostMove - count;
7657                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7658                         // adjust reversible move counter for checks in Xiangqi
7659                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7660                         if(i < backwardMostMove) i = backwardMostMove;
7661                         while(i <= forwardMostMove) {
7662                                 lastCheck = inCheck; // check evasion does not count
7663                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7664                                 if(inCheck || lastCheck) count--; // check does not count
7665                                 i++;
7666                         }
7667                 }
7668                 if( count >= 100)
7669                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7670                          /* this is used to judge if draw claims are legal */
7671                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7672                          if(engineOpponent) {
7673                            SendToProgram("force\n", engineOpponent); // suppress reply
7674                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7675                          }
7676                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7677                          return 1;
7678                 }
7679
7680                 /* if draw offer is pending, treat it as a draw claim
7681                  * when draw condition present, to allow engines a way to
7682                  * claim draws before making their move to avoid a race
7683                  * condition occurring after their move
7684                  */
7685                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7686                          char *p = NULL;
7687                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7688                              p = "Draw claim: 50-move rule";
7689                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7690                              p = "Draw claim: 3-fold repetition";
7691                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7692                              p = "Draw claim: insufficient mating material";
7693                          if( p != NULL && canAdjudicate) {
7694                              if(engineOpponent) {
7695                                SendToProgram("force\n", engineOpponent); // suppress reply
7696                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7697                              }
7698                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7699                              return 1;
7700                          }
7701                 }
7702
7703                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7704                     if(engineOpponent) {
7705                       SendToProgram("force\n", engineOpponent); // suppress reply
7706                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7707                     }
7708                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7709                     return 1;
7710                 }
7711         return 0;
7712 }
7713
7714 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7715 {   // [HGM] book: this routine intercepts moves to simulate book replies
7716     char *bookHit = NULL;
7717
7718     //first determine if the incoming move brings opponent into his book
7719     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7720         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7721     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7722     if(bookHit != NULL && !cps->bookSuspend) {
7723         // make sure opponent is not going to reply after receiving move to book position
7724         SendToProgram("force\n", cps);
7725         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7726     }
7727     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7728     // now arrange restart after book miss
7729     if(bookHit) {
7730         // after a book hit we never send 'go', and the code after the call to this routine
7731         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7732         char buf[MSG_SIZ], *move = bookHit;
7733         if(cps->useSAN) {
7734             int fromX, fromY, toX, toY;
7735             char promoChar;
7736             ChessMove moveType;
7737             move = buf + 30;
7738             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7739                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7740                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7741                                     PosFlags(forwardMostMove),
7742                                     fromY, fromX, toY, toX, promoChar, move);
7743             } else {
7744                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7745                 bookHit = NULL;
7746             }
7747         }
7748         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7749         SendToProgram(buf, cps);
7750         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7751     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7752         SendToProgram("go\n", cps);
7753         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7754     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7755         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7756             SendToProgram("go\n", cps);
7757         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7758     }
7759     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7760 }
7761
7762 char *savedMessage;
7763 ChessProgramState *savedState;
7764 void DeferredBookMove(void)
7765 {
7766         if(savedState->lastPing != savedState->lastPong)
7767                     ScheduleDelayedEvent(DeferredBookMove, 10);
7768         else
7769         HandleMachineMove(savedMessage, savedState);
7770 }
7771
7772 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7773
7774 void
7775 HandleMachineMove(message, cps)
7776      char *message;
7777      ChessProgramState *cps;
7778 {
7779     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7780     char realname[MSG_SIZ];
7781     int fromX, fromY, toX, toY;
7782     ChessMove moveType;
7783     char promoChar;
7784     char *p, *pv=buf1;
7785     int machineWhite;
7786     char *bookHit;
7787
7788     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7789         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7790         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7791             DisplayError(_("Invalid pairing from pairing engine"), 0);
7792             return;
7793         }
7794         pairingReceived = 1;
7795         NextMatchGame();
7796         return; // Skim the pairing messages here.
7797     }
7798
7799     cps->userError = 0;
7800
7801 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7802     /*
7803      * Kludge to ignore BEL characters
7804      */
7805     while (*message == '\007') message++;
7806
7807     /*
7808      * [HGM] engine debug message: ignore lines starting with '#' character
7809      */
7810     if(cps->debug && *message == '#') return;
7811
7812     /*
7813      * Look for book output
7814      */
7815     if (cps == &first && bookRequested) {
7816         if (message[0] == '\t' || message[0] == ' ') {
7817             /* Part of the book output is here; append it */
7818             strcat(bookOutput, message);
7819             strcat(bookOutput, "  \n");
7820             return;
7821         } else if (bookOutput[0] != NULLCHAR) {
7822             /* All of book output has arrived; display it */
7823             char *p = bookOutput;
7824             while (*p != NULLCHAR) {
7825                 if (*p == '\t') *p = ' ';
7826                 p++;
7827             }
7828             DisplayInformation(bookOutput);
7829             bookRequested = FALSE;
7830             /* Fall through to parse the current output */
7831         }
7832     }
7833
7834     /*
7835      * Look for machine move.
7836      */
7837     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7838         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7839     {
7840         /* This method is only useful on engines that support ping */
7841         if (cps->lastPing != cps->lastPong) {
7842           if (gameMode == BeginningOfGame) {
7843             /* Extra move from before last new; ignore */
7844             if (appData.debugMode) {
7845                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7846             }
7847           } else {
7848             if (appData.debugMode) {
7849                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7850                         cps->which, gameMode);
7851             }
7852
7853             SendToProgram("undo\n", cps);
7854           }
7855           return;
7856         }
7857
7858         switch (gameMode) {
7859           case BeginningOfGame:
7860             /* Extra move from before last reset; ignore */
7861             if (appData.debugMode) {
7862                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7863             }
7864             return;
7865
7866           case EndOfGame:
7867           case IcsIdle:
7868           default:
7869             /* Extra move after we tried to stop.  The mode test is
7870                not a reliable way of detecting this problem, but it's
7871                the best we can do on engines that don't support ping.
7872             */
7873             if (appData.debugMode) {
7874                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7875                         cps->which, gameMode);
7876             }
7877             SendToProgram("undo\n", cps);
7878             return;
7879
7880           case MachinePlaysWhite:
7881           case IcsPlayingWhite:
7882             machineWhite = TRUE;
7883             break;
7884
7885           case MachinePlaysBlack:
7886           case IcsPlayingBlack:
7887             machineWhite = FALSE;
7888             break;
7889
7890           case TwoMachinesPlay:
7891             machineWhite = (cps->twoMachinesColor[0] == 'w');
7892             break;
7893         }
7894         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7895             if (appData.debugMode) {
7896                 fprintf(debugFP,
7897                         "Ignoring move out of turn by %s, gameMode %d"
7898                         ", forwardMost %d\n",
7899                         cps->which, gameMode, forwardMostMove);
7900             }
7901             return;
7902         }
7903
7904     if (appData.debugMode) { int f = forwardMostMove;
7905         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7906                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7907                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7908     }
7909         if(cps->alphaRank) AlphaRank(machineMove, 4);
7910         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7911                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7912             /* Machine move could not be parsed; ignore it. */
7913           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7914                     machineMove, _(cps->which));
7915             DisplayError(buf1, 0);
7916             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7917                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7918             if (gameMode == TwoMachinesPlay) {
7919               GameEnds(machineWhite ? BlackWins : WhiteWins,
7920                        buf1, GE_XBOARD);
7921             }
7922             return;
7923         }
7924
7925         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7926         /* So we have to redo legality test with true e.p. status here,  */
7927         /* to make sure an illegal e.p. capture does not slip through,   */
7928         /* to cause a forfeit on a justified illegal-move complaint      */
7929         /* of the opponent.                                              */
7930         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7931            ChessMove moveType;
7932            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7933                              fromY, fromX, toY, toX, promoChar);
7934             if (appData.debugMode) {
7935                 int i;
7936                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7937                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7938                 fprintf(debugFP, "castling rights\n");
7939             }
7940             if(moveType == IllegalMove) {
7941               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7942                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7943                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7944                            buf1, GE_XBOARD);
7945                 return;
7946            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7947            /* [HGM] Kludge to handle engines that send FRC-style castling
7948               when they shouldn't (like TSCP-Gothic) */
7949            switch(moveType) {
7950              case WhiteASideCastleFR:
7951              case BlackASideCastleFR:
7952                toX+=2;
7953                currentMoveString[2]++;
7954                break;
7955              case WhiteHSideCastleFR:
7956              case BlackHSideCastleFR:
7957                toX--;
7958                currentMoveString[2]--;
7959                break;
7960              default: ; // nothing to do, but suppresses warning of pedantic compilers
7961            }
7962         }
7963         hintRequested = FALSE;
7964         lastHint[0] = NULLCHAR;
7965         bookRequested = FALSE;
7966         /* Program may be pondering now */
7967         cps->maybeThinking = TRUE;
7968         if (cps->sendTime == 2) cps->sendTime = 1;
7969         if (cps->offeredDraw) cps->offeredDraw--;
7970
7971         /* [AS] Save move info*/
7972         pvInfoList[ forwardMostMove ].score = programStats.score;
7973         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7974         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7975
7976         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7977
7978         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7979         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7980             int count = 0;
7981
7982             while( count < adjudicateLossPlies ) {
7983                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7984
7985                 if( count & 1 ) {
7986                     score = -score; /* Flip score for winning side */
7987                 }
7988
7989                 if( score > adjudicateLossThreshold ) {
7990                     break;
7991                 }
7992
7993                 count++;
7994             }
7995
7996             if( count >= adjudicateLossPlies ) {
7997                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7998
7999                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8000                     "Xboard adjudication",
8001                     GE_XBOARD );
8002
8003                 return;
8004             }
8005         }
8006
8007         if(Adjudicate(cps)) {
8008             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8009             return; // [HGM] adjudicate: for all automatic game ends
8010         }
8011
8012 #if ZIPPY
8013         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8014             first.initDone) {
8015           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8016                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8017                 SendToICS("draw ");
8018                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8019           }
8020           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8021           ics_user_moved = 1;
8022           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8023                 char buf[3*MSG_SIZ];
8024
8025                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8026                         programStats.score / 100.,
8027                         programStats.depth,
8028                         programStats.time / 100.,
8029                         (unsigned int)programStats.nodes,
8030                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8031                         programStats.movelist);
8032                 SendToICS(buf);
8033 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8034           }
8035         }
8036 #endif
8037
8038         /* [AS] Clear stats for next move */
8039         ClearProgramStats();
8040         thinkOutput[0] = NULLCHAR;
8041         hiddenThinkOutputState = 0;
8042
8043         bookHit = NULL;
8044         if (gameMode == TwoMachinesPlay) {
8045             /* [HGM] relaying draw offers moved to after reception of move */
8046             /* and interpreting offer as claim if it brings draw condition */
8047             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8048                 SendToProgram("draw\n", cps->other);
8049             }
8050             if (cps->other->sendTime) {
8051                 SendTimeRemaining(cps->other,
8052                                   cps->other->twoMachinesColor[0] == 'w');
8053             }
8054             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8055             if (firstMove && !bookHit) {
8056                 firstMove = FALSE;
8057                 if (cps->other->useColors) {
8058                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8059                 }
8060                 SendToProgram("go\n", cps->other);
8061             }
8062             cps->other->maybeThinking = TRUE;
8063         }
8064
8065         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8066
8067         if (!pausing && appData.ringBellAfterMoves) {
8068             RingBell();
8069         }
8070
8071         /*
8072          * Reenable menu items that were disabled while
8073          * machine was thinking
8074          */
8075         if (gameMode != TwoMachinesPlay)
8076             SetUserThinkingEnables();
8077
8078         // [HGM] book: after book hit opponent has received move and is now in force mode
8079         // force the book reply into it, and then fake that it outputted this move by jumping
8080         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8081         if(bookHit) {
8082                 static char bookMove[MSG_SIZ]; // a bit generous?
8083
8084                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8085                 strcat(bookMove, bookHit);
8086                 message = bookMove;
8087                 cps = cps->other;
8088                 programStats.nodes = programStats.depth = programStats.time =
8089                 programStats.score = programStats.got_only_move = 0;
8090                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8091
8092                 if(cps->lastPing != cps->lastPong) {
8093                     savedMessage = message; // args for deferred call
8094                     savedState = cps;
8095                     ScheduleDelayedEvent(DeferredBookMove, 10);
8096                     return;
8097                 }
8098                 goto FakeBookMove;
8099         }
8100
8101         return;
8102     }
8103
8104     /* Set special modes for chess engines.  Later something general
8105      *  could be added here; for now there is just one kludge feature,
8106      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8107      *  when "xboard" is given as an interactive command.
8108      */
8109     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8110         cps->useSigint = FALSE;
8111         cps->useSigterm = FALSE;
8112     }
8113     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8114       ParseFeatures(message+8, cps);
8115       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8116     }
8117
8118     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8119       int dummy, s=6; char buf[MSG_SIZ];
8120       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8121       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8122       ParseFEN(boards[0], &dummy, message+s);
8123       DrawPosition(TRUE, boards[0]);
8124       startedFromSetupPosition = TRUE;
8125       return;
8126     }
8127     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8128      * want this, I was asked to put it in, and obliged.
8129      */
8130     if (!strncmp(message, "setboard ", 9)) {
8131         Board initial_position;
8132
8133         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8134
8135         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8136             DisplayError(_("Bad FEN received from engine"), 0);
8137             return ;
8138         } else {
8139            Reset(TRUE, FALSE);
8140            CopyBoard(boards[0], initial_position);
8141            initialRulePlies = FENrulePlies;
8142            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8143            else gameMode = MachinePlaysBlack;
8144            DrawPosition(FALSE, boards[currentMove]);
8145         }
8146         return;
8147     }
8148
8149     /*
8150      * Look for communication commands
8151      */
8152     if (!strncmp(message, "telluser ", 9)) {
8153         if(message[9] == '\\' && message[10] == '\\')
8154             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8155         PlayTellSound();
8156         DisplayNote(message + 9);
8157         return;
8158     }
8159     if (!strncmp(message, "tellusererror ", 14)) {
8160         cps->userError = 1;
8161         if(message[14] == '\\' && message[15] == '\\')
8162             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8163         PlayTellSound();
8164         DisplayError(message + 14, 0);
8165         return;
8166     }
8167     if (!strncmp(message, "tellopponent ", 13)) {
8168       if (appData.icsActive) {
8169         if (loggedOn) {
8170           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8171           SendToICS(buf1);
8172         }
8173       } else {
8174         DisplayNote(message + 13);
8175       }
8176       return;
8177     }
8178     if (!strncmp(message, "tellothers ", 11)) {
8179       if (appData.icsActive) {
8180         if (loggedOn) {
8181           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8182           SendToICS(buf1);
8183         }
8184       }
8185       return;
8186     }
8187     if (!strncmp(message, "tellall ", 8)) {
8188       if (appData.icsActive) {
8189         if (loggedOn) {
8190           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8191           SendToICS(buf1);
8192         }
8193       } else {
8194         DisplayNote(message + 8);
8195       }
8196       return;
8197     }
8198     if (strncmp(message, "warning", 7) == 0) {
8199         /* Undocumented feature, use tellusererror in new code */
8200         DisplayError(message, 0);
8201         return;
8202     }
8203     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8204         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8205         strcat(realname, " query");
8206         AskQuestion(realname, buf2, buf1, cps->pr);
8207         return;
8208     }
8209     /* Commands from the engine directly to ICS.  We don't allow these to be
8210      *  sent until we are logged on. Crafty kibitzes have been known to
8211      *  interfere with the login process.
8212      */
8213     if (loggedOn) {
8214         if (!strncmp(message, "tellics ", 8)) {
8215             SendToICS(message + 8);
8216             SendToICS("\n");
8217             return;
8218         }
8219         if (!strncmp(message, "tellicsnoalias ", 15)) {
8220             SendToICS(ics_prefix);
8221             SendToICS(message + 15);
8222             SendToICS("\n");
8223             return;
8224         }
8225         /* The following are for backward compatibility only */
8226         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8227             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8228             SendToICS(ics_prefix);
8229             SendToICS(message);
8230             SendToICS("\n");
8231             return;
8232         }
8233     }
8234     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8235         return;
8236     }
8237     /*
8238      * If the move is illegal, cancel it and redraw the board.
8239      * Also deal with other error cases.  Matching is rather loose
8240      * here to accommodate engines written before the spec.
8241      */
8242     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8243         strncmp(message, "Error", 5) == 0) {
8244         if (StrStr(message, "name") ||
8245             StrStr(message, "rating") || StrStr(message, "?") ||
8246             StrStr(message, "result") || StrStr(message, "board") ||
8247             StrStr(message, "bk") || StrStr(message, "computer") ||
8248             StrStr(message, "variant") || StrStr(message, "hint") ||
8249             StrStr(message, "random") || StrStr(message, "depth") ||
8250             StrStr(message, "accepted")) {
8251             return;
8252         }
8253         if (StrStr(message, "protover")) {
8254           /* Program is responding to input, so it's apparently done
8255              initializing, and this error message indicates it is
8256              protocol version 1.  So we don't need to wait any longer
8257              for it to initialize and send feature commands. */
8258           FeatureDone(cps, 1);
8259           cps->protocolVersion = 1;
8260           return;
8261         }
8262         cps->maybeThinking = FALSE;
8263
8264         if (StrStr(message, "draw")) {
8265             /* Program doesn't have "draw" command */
8266             cps->sendDrawOffers = 0;
8267             return;
8268         }
8269         if (cps->sendTime != 1 &&
8270             (StrStr(message, "time") || StrStr(message, "otim"))) {
8271           /* Program apparently doesn't have "time" or "otim" command */
8272           cps->sendTime = 0;
8273           return;
8274         }
8275         if (StrStr(message, "analyze")) {
8276             cps->analysisSupport = FALSE;
8277             cps->analyzing = FALSE;
8278 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8279             EditGameEvent(); // [HGM] try to preserve loaded game
8280             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8281             DisplayError(buf2, 0);
8282             return;
8283         }
8284         if (StrStr(message, "(no matching move)st")) {
8285           /* Special kludge for GNU Chess 4 only */
8286           cps->stKludge = TRUE;
8287           SendTimeControl(cps, movesPerSession, timeControl,
8288                           timeIncrement, appData.searchDepth,
8289                           searchTime);
8290           return;
8291         }
8292         if (StrStr(message, "(no matching move)sd")) {
8293           /* Special kludge for GNU Chess 4 only */
8294           cps->sdKludge = TRUE;
8295           SendTimeControl(cps, movesPerSession, timeControl,
8296                           timeIncrement, appData.searchDepth,
8297                           searchTime);
8298           return;
8299         }
8300         if (!StrStr(message, "llegal")) {
8301             return;
8302         }
8303         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8304             gameMode == IcsIdle) return;
8305         if (forwardMostMove <= backwardMostMove) return;
8306         if (pausing) PauseEvent();
8307       if(appData.forceIllegal) {
8308             // [HGM] illegal: machine refused move; force position after move into it
8309           SendToProgram("force\n", cps);
8310           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8311                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8312                 // when black is to move, while there might be nothing on a2 or black
8313                 // might already have the move. So send the board as if white has the move.
8314                 // But first we must change the stm of the engine, as it refused the last move
8315                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8316                 if(WhiteOnMove(forwardMostMove)) {
8317                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8318                     SendBoard(cps, forwardMostMove); // kludgeless board
8319                 } else {
8320                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8321                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8322                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8323                 }
8324           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8325             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8326                  gameMode == TwoMachinesPlay)
8327               SendToProgram("go\n", cps);
8328             return;
8329       } else
8330         if (gameMode == PlayFromGameFile) {
8331             /* Stop reading this game file */
8332             gameMode = EditGame;
8333             ModeHighlight();
8334         }
8335         /* [HGM] illegal-move claim should forfeit game when Xboard */
8336         /* only passes fully legal moves                            */
8337         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8338             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8339                                 "False illegal-move claim", GE_XBOARD );
8340             return; // do not take back move we tested as valid
8341         }
8342         currentMove = forwardMostMove-1;
8343         DisplayMove(currentMove-1); /* before DisplayMoveError */
8344         SwitchClocks(forwardMostMove-1); // [HGM] race
8345         DisplayBothClocks();
8346         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8347                 parseList[currentMove], _(cps->which));
8348         DisplayMoveError(buf1);
8349         DrawPosition(FALSE, boards[currentMove]);
8350         return;
8351     }
8352     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8353         /* Program has a broken "time" command that
8354            outputs a string not ending in newline.
8355            Don't use it. */
8356         cps->sendTime = 0;
8357     }
8358
8359     /*
8360      * If chess program startup fails, exit with an error message.
8361      * Attempts to recover here are futile.
8362      */
8363     if ((StrStr(message, "unknown host") != NULL)
8364         || (StrStr(message, "No remote directory") != NULL)
8365         || (StrStr(message, "not found") != NULL)
8366         || (StrStr(message, "No such file") != NULL)
8367         || (StrStr(message, "can't alloc") != NULL)
8368         || (StrStr(message, "Permission denied") != NULL)) {
8369
8370         cps->maybeThinking = FALSE;
8371         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8372                 _(cps->which), cps->program, cps->host, message);
8373         RemoveInputSource(cps->isr);
8374         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8375             if(cps == &first) appData.noChessProgram = TRUE;
8376             DisplayError(buf1, 0);
8377         }
8378         return;
8379     }
8380
8381     /*
8382      * Look for hint output
8383      */
8384     if (sscanf(message, "Hint: %s", buf1) == 1) {
8385         if (cps == &first && hintRequested) {
8386             hintRequested = FALSE;
8387             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8388                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8389                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8390                                     PosFlags(forwardMostMove),
8391                                     fromY, fromX, toY, toX, promoChar, buf1);
8392                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8393                 DisplayInformation(buf2);
8394             } else {
8395                 /* Hint move could not be parsed!? */
8396               snprintf(buf2, sizeof(buf2),
8397                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8398                         buf1, _(cps->which));
8399                 DisplayError(buf2, 0);
8400             }
8401         } else {
8402           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8403         }
8404         return;
8405     }
8406
8407     /*
8408      * Ignore other messages if game is not in progress
8409      */
8410     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8411         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8412
8413     /*
8414      * look for win, lose, draw, or draw offer
8415      */
8416     if (strncmp(message, "1-0", 3) == 0) {
8417         char *p, *q, *r = "";
8418         p = strchr(message, '{');
8419         if (p) {
8420             q = strchr(p, '}');
8421             if (q) {
8422                 *q = NULLCHAR;
8423                 r = p + 1;
8424             }
8425         }
8426         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8427         return;
8428     } else if (strncmp(message, "0-1", 3) == 0) {
8429         char *p, *q, *r = "";
8430         p = strchr(message, '{');
8431         if (p) {
8432             q = strchr(p, '}');
8433             if (q) {
8434                 *q = NULLCHAR;
8435                 r = p + 1;
8436             }
8437         }
8438         /* Kludge for Arasan 4.1 bug */
8439         if (strcmp(r, "Black resigns") == 0) {
8440             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8441             return;
8442         }
8443         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8444         return;
8445     } else if (strncmp(message, "1/2", 3) == 0) {
8446         char *p, *q, *r = "";
8447         p = strchr(message, '{');
8448         if (p) {
8449             q = strchr(p, '}');
8450             if (q) {
8451                 *q = NULLCHAR;
8452                 r = p + 1;
8453             }
8454         }
8455
8456         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8457         return;
8458
8459     } else if (strncmp(message, "White resign", 12) == 0) {
8460         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8461         return;
8462     } else if (strncmp(message, "Black resign", 12) == 0) {
8463         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8464         return;
8465     } else if (strncmp(message, "White matches", 13) == 0 ||
8466                strncmp(message, "Black matches", 13) == 0   ) {
8467         /* [HGM] ignore GNUShogi noises */
8468         return;
8469     } else if (strncmp(message, "White", 5) == 0 &&
8470                message[5] != '(' &&
8471                StrStr(message, "Black") == NULL) {
8472         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8473         return;
8474     } else if (strncmp(message, "Black", 5) == 0 &&
8475                message[5] != '(') {
8476         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8477         return;
8478     } else if (strcmp(message, "resign") == 0 ||
8479                strcmp(message, "computer resigns") == 0) {
8480         switch (gameMode) {
8481           case MachinePlaysBlack:
8482           case IcsPlayingBlack:
8483             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8484             break;
8485           case MachinePlaysWhite:
8486           case IcsPlayingWhite:
8487             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8488             break;
8489           case TwoMachinesPlay:
8490             if (cps->twoMachinesColor[0] == 'w')
8491               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8492             else
8493               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8494             break;
8495           default:
8496             /* can't happen */
8497             break;
8498         }
8499         return;
8500     } else if (strncmp(message, "opponent mates", 14) == 0) {
8501         switch (gameMode) {
8502           case MachinePlaysBlack:
8503           case IcsPlayingBlack:
8504             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8505             break;
8506           case MachinePlaysWhite:
8507           case IcsPlayingWhite:
8508             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8509             break;
8510           case TwoMachinesPlay:
8511             if (cps->twoMachinesColor[0] == 'w')
8512               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8513             else
8514               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8515             break;
8516           default:
8517             /* can't happen */
8518             break;
8519         }
8520         return;
8521     } else if (strncmp(message, "computer mates", 14) == 0) {
8522         switch (gameMode) {
8523           case MachinePlaysBlack:
8524           case IcsPlayingBlack:
8525             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8526             break;
8527           case MachinePlaysWhite:
8528           case IcsPlayingWhite:
8529             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8530             break;
8531           case TwoMachinesPlay:
8532             if (cps->twoMachinesColor[0] == 'w')
8533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8534             else
8535               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8536             break;
8537           default:
8538             /* can't happen */
8539             break;
8540         }
8541         return;
8542     } else if (strncmp(message, "checkmate", 9) == 0) {
8543         if (WhiteOnMove(forwardMostMove)) {
8544             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8545         } else {
8546             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8547         }
8548         return;
8549     } else if (strstr(message, "Draw") != NULL ||
8550                strstr(message, "game is a draw") != NULL) {
8551         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8552         return;
8553     } else if (strstr(message, "offer") != NULL &&
8554                strstr(message, "draw") != NULL) {
8555 #if ZIPPY
8556         if (appData.zippyPlay && first.initDone) {
8557             /* Relay offer to ICS */
8558             SendToICS(ics_prefix);
8559             SendToICS("draw\n");
8560         }
8561 #endif
8562         cps->offeredDraw = 2; /* valid until this engine moves twice */
8563         if (gameMode == TwoMachinesPlay) {
8564             if (cps->other->offeredDraw) {
8565                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8566             /* [HGM] in two-machine mode we delay relaying draw offer      */
8567             /* until after we also have move, to see if it is really claim */
8568             }
8569         } else if (gameMode == MachinePlaysWhite ||
8570                    gameMode == MachinePlaysBlack) {
8571           if (userOfferedDraw) {
8572             DisplayInformation(_("Machine accepts your draw offer"));
8573             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8574           } else {
8575             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8576           }
8577         }
8578     }
8579
8580
8581     /*
8582      * Look for thinking output
8583      */
8584     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8585           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8586                                 ) {
8587         int plylev, mvleft, mvtot, curscore, time;
8588         char mvname[MOVE_LEN];
8589         u64 nodes; // [DM]
8590         char plyext;
8591         int ignore = FALSE;
8592         int prefixHint = FALSE;
8593         mvname[0] = NULLCHAR;
8594
8595         switch (gameMode) {
8596           case MachinePlaysBlack:
8597           case IcsPlayingBlack:
8598             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8599             break;
8600           case MachinePlaysWhite:
8601           case IcsPlayingWhite:
8602             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8603             break;
8604           case AnalyzeMode:
8605           case AnalyzeFile:
8606             break;
8607           case IcsObserving: /* [DM] icsEngineAnalyze */
8608             if (!appData.icsEngineAnalyze) ignore = TRUE;
8609             break;
8610           case TwoMachinesPlay:
8611             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8612                 ignore = TRUE;
8613             }
8614             break;
8615           default:
8616             ignore = TRUE;
8617             break;
8618         }
8619
8620         if (!ignore) {
8621             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8622             buf1[0] = NULLCHAR;
8623             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8624                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8625
8626                 if (plyext != ' ' && plyext != '\t') {
8627                     time *= 100;
8628                 }
8629
8630                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8631                 if( cps->scoreIsAbsolute &&
8632                     ( gameMode == MachinePlaysBlack ||
8633                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8634                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8635                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8636                      !WhiteOnMove(currentMove)
8637                     ) )
8638                 {
8639                     curscore = -curscore;
8640                 }
8641
8642                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8643
8644                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8645                         char buf[MSG_SIZ];
8646                         FILE *f;
8647                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8648                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8649                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8650                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8651                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8652                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8653                                 fclose(f);
8654                         } else DisplayError("failed writing PV", 0);
8655                 }
8656
8657                 tempStats.depth = plylev;
8658                 tempStats.nodes = nodes;
8659                 tempStats.time = time;
8660                 tempStats.score = curscore;
8661                 tempStats.got_only_move = 0;
8662
8663                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8664                         int ticklen;
8665
8666                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8667                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8668                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8669                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8670                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8671                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8672                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8673                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8674                 }
8675
8676                 /* Buffer overflow protection */
8677                 if (pv[0] != NULLCHAR) {
8678                     if (strlen(pv) >= sizeof(tempStats.movelist)
8679                         && appData.debugMode) {
8680                         fprintf(debugFP,
8681                                 "PV is too long; using the first %u bytes.\n",
8682                                 (unsigned) sizeof(tempStats.movelist) - 1);
8683                     }
8684
8685                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8686                 } else {
8687                     sprintf(tempStats.movelist, " no PV\n");
8688                 }
8689
8690                 if (tempStats.seen_stat) {
8691                     tempStats.ok_to_send = 1;
8692                 }
8693
8694                 if (strchr(tempStats.movelist, '(') != NULL) {
8695                     tempStats.line_is_book = 1;
8696                     tempStats.nr_moves = 0;
8697                     tempStats.moves_left = 0;
8698                 } else {
8699                     tempStats.line_is_book = 0;
8700                 }
8701
8702                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8703                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8704
8705                 SendProgramStatsToFrontend( cps, &tempStats );
8706
8707                 /*
8708                     [AS] Protect the thinkOutput buffer from overflow... this
8709                     is only useful if buf1 hasn't overflowed first!
8710                 */
8711                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8712                          plylev,
8713                          (gameMode == TwoMachinesPlay ?
8714                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8715                          ((double) curscore) / 100.0,
8716                          prefixHint ? lastHint : "",
8717                          prefixHint ? " " : "" );
8718
8719                 if( buf1[0] != NULLCHAR ) {
8720                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8721
8722                     if( strlen(pv) > max_len ) {
8723                         if( appData.debugMode) {
8724                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8725                         }
8726                         pv[max_len+1] = '\0';
8727                     }
8728
8729                     strcat( thinkOutput, pv);
8730                 }
8731
8732                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8733                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8734                     DisplayMove(currentMove - 1);
8735                 }
8736                 return;
8737
8738             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8739                 /* crafty (9.25+) says "(only move) <move>"
8740                  * if there is only 1 legal move
8741                  */
8742                 sscanf(p, "(only move) %s", buf1);
8743                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8744                 sprintf(programStats.movelist, "%s (only move)", buf1);
8745                 programStats.depth = 1;
8746                 programStats.nr_moves = 1;
8747                 programStats.moves_left = 1;
8748                 programStats.nodes = 1;
8749                 programStats.time = 1;
8750                 programStats.got_only_move = 1;
8751
8752                 /* Not really, but we also use this member to
8753                    mean "line isn't going to change" (Crafty
8754                    isn't searching, so stats won't change) */
8755                 programStats.line_is_book = 1;
8756
8757                 SendProgramStatsToFrontend( cps, &programStats );
8758
8759                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8760                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8761                     DisplayMove(currentMove - 1);
8762                 }
8763                 return;
8764             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8765                               &time, &nodes, &plylev, &mvleft,
8766                               &mvtot, mvname) >= 5) {
8767                 /* The stat01: line is from Crafty (9.29+) in response
8768                    to the "." command */
8769                 programStats.seen_stat = 1;
8770                 cps->maybeThinking = TRUE;
8771
8772                 if (programStats.got_only_move || !appData.periodicUpdates)
8773                   return;
8774
8775                 programStats.depth = plylev;
8776                 programStats.time = time;
8777                 programStats.nodes = nodes;
8778                 programStats.moves_left = mvleft;
8779                 programStats.nr_moves = mvtot;
8780                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8781                 programStats.ok_to_send = 1;
8782                 programStats.movelist[0] = '\0';
8783
8784                 SendProgramStatsToFrontend( cps, &programStats );
8785
8786                 return;
8787
8788             } else if (strncmp(message,"++",2) == 0) {
8789                 /* Crafty 9.29+ outputs this */
8790                 programStats.got_fail = 2;
8791                 return;
8792
8793             } else if (strncmp(message,"--",2) == 0) {
8794                 /* Crafty 9.29+ outputs this */
8795                 programStats.got_fail = 1;
8796                 return;
8797
8798             } else if (thinkOutput[0] != NULLCHAR &&
8799                        strncmp(message, "    ", 4) == 0) {
8800                 unsigned message_len;
8801
8802                 p = message;
8803                 while (*p && *p == ' ') p++;
8804
8805                 message_len = strlen( p );
8806
8807                 /* [AS] Avoid buffer overflow */
8808                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8809                     strcat(thinkOutput, " ");
8810                     strcat(thinkOutput, p);
8811                 }
8812
8813                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8814                     strcat(programStats.movelist, " ");
8815                     strcat(programStats.movelist, p);
8816                 }
8817
8818                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8819                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8820                     DisplayMove(currentMove - 1);
8821                 }
8822                 return;
8823             }
8824         }
8825         else {
8826             buf1[0] = NULLCHAR;
8827
8828             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8829                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8830             {
8831                 ChessProgramStats cpstats;
8832
8833                 if (plyext != ' ' && plyext != '\t') {
8834                     time *= 100;
8835                 }
8836
8837                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8838                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8839                     curscore = -curscore;
8840                 }
8841
8842                 cpstats.depth = plylev;
8843                 cpstats.nodes = nodes;
8844                 cpstats.time = time;
8845                 cpstats.score = curscore;
8846                 cpstats.got_only_move = 0;
8847                 cpstats.movelist[0] = '\0';
8848
8849                 if (buf1[0] != NULLCHAR) {
8850                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8851                 }
8852
8853                 cpstats.ok_to_send = 0;
8854                 cpstats.line_is_book = 0;
8855                 cpstats.nr_moves = 0;
8856                 cpstats.moves_left = 0;
8857
8858                 SendProgramStatsToFrontend( cps, &cpstats );
8859             }
8860         }
8861     }
8862 }
8863
8864
8865 /* Parse a game score from the character string "game", and
8866    record it as the history of the current game.  The game
8867    score is NOT assumed to start from the standard position.
8868    The display is not updated in any way.
8869    */
8870 void
8871 ParseGameHistory(game)
8872      char *game;
8873 {
8874     ChessMove moveType;
8875     int fromX, fromY, toX, toY, boardIndex;
8876     char promoChar;
8877     char *p, *q;
8878     char buf[MSG_SIZ];
8879
8880     if (appData.debugMode)
8881       fprintf(debugFP, "Parsing game history: %s\n", game);
8882
8883     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8884     gameInfo.site = StrSave(appData.icsHost);
8885     gameInfo.date = PGNDate();
8886     gameInfo.round = StrSave("-");
8887
8888     /* Parse out names of players */
8889     while (*game == ' ') game++;
8890     p = buf;
8891     while (*game != ' ') *p++ = *game++;
8892     *p = NULLCHAR;
8893     gameInfo.white = StrSave(buf);
8894     while (*game == ' ') game++;
8895     p = buf;
8896     while (*game != ' ' && *game != '\n') *p++ = *game++;
8897     *p = NULLCHAR;
8898     gameInfo.black = StrSave(buf);
8899
8900     /* Parse moves */
8901     boardIndex = blackPlaysFirst ? 1 : 0;
8902     yynewstr(game);
8903     for (;;) {
8904         yyboardindex = boardIndex;
8905         moveType = (ChessMove) Myylex();
8906         switch (moveType) {
8907           case IllegalMove:             /* maybe suicide chess, etc. */
8908   if (appData.debugMode) {
8909     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8910     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8911     setbuf(debugFP, NULL);
8912   }
8913           case WhitePromotion:
8914           case BlackPromotion:
8915           case WhiteNonPromotion:
8916           case BlackNonPromotion:
8917           case NormalMove:
8918           case WhiteCapturesEnPassant:
8919           case BlackCapturesEnPassant:
8920           case WhiteKingSideCastle:
8921           case WhiteQueenSideCastle:
8922           case BlackKingSideCastle:
8923           case BlackQueenSideCastle:
8924           case WhiteKingSideCastleWild:
8925           case WhiteQueenSideCastleWild:
8926           case BlackKingSideCastleWild:
8927           case BlackQueenSideCastleWild:
8928           /* PUSH Fabien */
8929           case WhiteHSideCastleFR:
8930           case WhiteASideCastleFR:
8931           case BlackHSideCastleFR:
8932           case BlackASideCastleFR:
8933           /* POP Fabien */
8934             fromX = currentMoveString[0] - AAA;
8935             fromY = currentMoveString[1] - ONE;
8936             toX = currentMoveString[2] - AAA;
8937             toY = currentMoveString[3] - ONE;
8938             promoChar = currentMoveString[4];
8939             break;
8940           case WhiteDrop:
8941           case BlackDrop:
8942             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8943             fromX = moveType == WhiteDrop ?
8944               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8945             (int) CharToPiece(ToLower(currentMoveString[0]));
8946             fromY = DROP_RANK;
8947             toX = currentMoveString[2] - AAA;
8948             toY = currentMoveString[3] - ONE;
8949             promoChar = NULLCHAR;
8950             break;
8951           case AmbiguousMove:
8952             /* bug? */
8953             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8954   if (appData.debugMode) {
8955     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8956     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8957     setbuf(debugFP, NULL);
8958   }
8959             DisplayError(buf, 0);
8960             return;
8961           case ImpossibleMove:
8962             /* bug? */
8963             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8964   if (appData.debugMode) {
8965     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8966     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8967     setbuf(debugFP, NULL);
8968   }
8969             DisplayError(buf, 0);
8970             return;
8971           case EndOfFile:
8972             if (boardIndex < backwardMostMove) {
8973                 /* Oops, gap.  How did that happen? */
8974                 DisplayError(_("Gap in move list"), 0);
8975                 return;
8976             }
8977             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8978             if (boardIndex > forwardMostMove) {
8979                 forwardMostMove = boardIndex;
8980             }
8981             return;
8982           case ElapsedTime:
8983             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8984                 strcat(parseList[boardIndex-1], " ");
8985                 strcat(parseList[boardIndex-1], yy_text);
8986             }
8987             continue;
8988           case Comment:
8989           case PGNTag:
8990           case NAG:
8991           default:
8992             /* ignore */
8993             continue;
8994           case WhiteWins:
8995           case BlackWins:
8996           case GameIsDrawn:
8997           case GameUnfinished:
8998             if (gameMode == IcsExamining) {
8999                 if (boardIndex < backwardMostMove) {
9000                     /* Oops, gap.  How did that happen? */
9001                     return;
9002                 }
9003                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9004                 return;
9005             }
9006             gameInfo.result = moveType;
9007             p = strchr(yy_text, '{');
9008             if (p == NULL) p = strchr(yy_text, '(');
9009             if (p == NULL) {
9010                 p = yy_text;
9011                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9012             } else {
9013                 q = strchr(p, *p == '{' ? '}' : ')');
9014                 if (q != NULL) *q = NULLCHAR;
9015                 p++;
9016             }
9017             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9018             gameInfo.resultDetails = StrSave(p);
9019             continue;
9020         }
9021         if (boardIndex >= forwardMostMove &&
9022             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9023             backwardMostMove = blackPlaysFirst ? 1 : 0;
9024             return;
9025         }
9026         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9027                                  fromY, fromX, toY, toX, promoChar,
9028                                  parseList[boardIndex]);
9029         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9030         /* currentMoveString is set as a side-effect of yylex */
9031         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9032         strcat(moveList[boardIndex], "\n");
9033         boardIndex++;
9034         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9035         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9036           case MT_NONE:
9037           case MT_STALEMATE:
9038           default:
9039             break;
9040           case MT_CHECK:
9041             if(gameInfo.variant != VariantShogi)
9042                 strcat(parseList[boardIndex - 1], "+");
9043             break;
9044           case MT_CHECKMATE:
9045           case MT_STAINMATE:
9046             strcat(parseList[boardIndex - 1], "#");
9047             break;
9048         }
9049     }
9050 }
9051
9052
9053 /* Apply a move to the given board  */
9054 void
9055 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9056      int fromX, fromY, toX, toY;
9057      int promoChar;
9058      Board board;
9059 {
9060   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9061   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9062
9063     /* [HGM] compute & store e.p. status and castling rights for new position */
9064     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9065
9066       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9067       oldEP = (signed char)board[EP_STATUS];
9068       board[EP_STATUS] = EP_NONE;
9069
9070   if (fromY == DROP_RANK) {
9071         /* must be first */
9072         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9073             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9074             return;
9075         }
9076         piece = board[toY][toX] = (ChessSquare) fromX;
9077   } else {
9078       int i;
9079
9080       if( board[toY][toX] != EmptySquare )
9081            board[EP_STATUS] = EP_CAPTURE;
9082
9083       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9084            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9085                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9086       } else
9087       if( board[fromY][fromX] == WhitePawn ) {
9088            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9089                board[EP_STATUS] = EP_PAWN_MOVE;
9090            if( toY-fromY==2) {
9091                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9092                         gameInfo.variant != VariantBerolina || toX < fromX)
9093                       board[EP_STATUS] = toX | berolina;
9094                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9095                         gameInfo.variant != VariantBerolina || toX > fromX)
9096                       board[EP_STATUS] = toX;
9097            }
9098       } else
9099       if( board[fromY][fromX] == BlackPawn ) {
9100            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9101                board[EP_STATUS] = EP_PAWN_MOVE;
9102            if( toY-fromY== -2) {
9103                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9104                         gameInfo.variant != VariantBerolina || toX < fromX)
9105                       board[EP_STATUS] = toX | berolina;
9106                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9107                         gameInfo.variant != VariantBerolina || toX > fromX)
9108                       board[EP_STATUS] = toX;
9109            }
9110        }
9111
9112        for(i=0; i<nrCastlingRights; i++) {
9113            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9114               board[CASTLING][i] == toX   && castlingRank[i] == toY
9115              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9116        }
9117
9118      if (fromX == toX && fromY == toY) return;
9119
9120      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9121      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9122      if(gameInfo.variant == VariantKnightmate)
9123          king += (int) WhiteUnicorn - (int) WhiteKing;
9124
9125     /* Code added by Tord: */
9126     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9127     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9128         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9129       board[fromY][fromX] = EmptySquare;
9130       board[toY][toX] = EmptySquare;
9131       if((toX > fromX) != (piece == WhiteRook)) {
9132         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9133       } else {
9134         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9135       }
9136     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9137                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9138       board[fromY][fromX] = EmptySquare;
9139       board[toY][toX] = EmptySquare;
9140       if((toX > fromX) != (piece == BlackRook)) {
9141         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9142       } else {
9143         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9144       }
9145     /* End of code added by Tord */
9146
9147     } else if (board[fromY][fromX] == king
9148         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9149         && toY == fromY && toX > fromX+1) {
9150         board[fromY][fromX] = EmptySquare;
9151         board[toY][toX] = king;
9152         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9153         board[fromY][BOARD_RGHT-1] = EmptySquare;
9154     } else if (board[fromY][fromX] == king
9155         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9156                && toY == fromY && toX < fromX-1) {
9157         board[fromY][fromX] = EmptySquare;
9158         board[toY][toX] = king;
9159         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9160         board[fromY][BOARD_LEFT] = EmptySquare;
9161     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9162                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9163                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9164                ) {
9165         /* white pawn promotion */
9166         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9167         if(gameInfo.variant==VariantBughouse ||
9168            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9169             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9170         board[fromY][fromX] = EmptySquare;
9171     } else if ((fromY >= BOARD_HEIGHT>>1)
9172                && (toX != fromX)
9173                && gameInfo.variant != VariantXiangqi
9174                && gameInfo.variant != VariantBerolina
9175                && (board[fromY][fromX] == WhitePawn)
9176                && (board[toY][toX] == EmptySquare)) {
9177         board[fromY][fromX] = EmptySquare;
9178         board[toY][toX] = WhitePawn;
9179         captured = board[toY - 1][toX];
9180         board[toY - 1][toX] = EmptySquare;
9181     } else if ((fromY == BOARD_HEIGHT-4)
9182                && (toX == fromX)
9183                && gameInfo.variant == VariantBerolina
9184                && (board[fromY][fromX] == WhitePawn)
9185                && (board[toY][toX] == EmptySquare)) {
9186         board[fromY][fromX] = EmptySquare;
9187         board[toY][toX] = WhitePawn;
9188         if(oldEP & EP_BEROLIN_A) {
9189                 captured = board[fromY][fromX-1];
9190                 board[fromY][fromX-1] = EmptySquare;
9191         }else{  captured = board[fromY][fromX+1];
9192                 board[fromY][fromX+1] = EmptySquare;
9193         }
9194     } else if (board[fromY][fromX] == king
9195         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9196                && toY == fromY && toX > fromX+1) {
9197         board[fromY][fromX] = EmptySquare;
9198         board[toY][toX] = king;
9199         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9200         board[fromY][BOARD_RGHT-1] = EmptySquare;
9201     } else if (board[fromY][fromX] == king
9202         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9203                && toY == fromY && toX < fromX-1) {
9204         board[fromY][fromX] = EmptySquare;
9205         board[toY][toX] = king;
9206         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9207         board[fromY][BOARD_LEFT] = EmptySquare;
9208     } else if (fromY == 7 && fromX == 3
9209                && board[fromY][fromX] == BlackKing
9210                && toY == 7 && toX == 5) {
9211         board[fromY][fromX] = EmptySquare;
9212         board[toY][toX] = BlackKing;
9213         board[fromY][7] = EmptySquare;
9214         board[toY][4] = BlackRook;
9215     } else if (fromY == 7 && fromX == 3
9216                && board[fromY][fromX] == BlackKing
9217                && toY == 7 && toX == 1) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = BlackKing;
9220         board[fromY][0] = EmptySquare;
9221         board[toY][2] = BlackRook;
9222     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9223                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9224                && toY < promoRank && promoChar
9225                ) {
9226         /* black pawn promotion */
9227         board[toY][toX] = CharToPiece(ToLower(promoChar));
9228         if(gameInfo.variant==VariantBughouse ||
9229            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9230             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9231         board[fromY][fromX] = EmptySquare;
9232     } else if ((fromY < BOARD_HEIGHT>>1)
9233                && (toX != fromX)
9234                && gameInfo.variant != VariantXiangqi
9235                && gameInfo.variant != VariantBerolina
9236                && (board[fromY][fromX] == BlackPawn)
9237                && (board[toY][toX] == EmptySquare)) {
9238         board[fromY][fromX] = EmptySquare;
9239         board[toY][toX] = BlackPawn;
9240         captured = board[toY + 1][toX];
9241         board[toY + 1][toX] = EmptySquare;
9242     } else if ((fromY == 3)
9243                && (toX == fromX)
9244                && gameInfo.variant == VariantBerolina
9245                && (board[fromY][fromX] == BlackPawn)
9246                && (board[toY][toX] == EmptySquare)) {
9247         board[fromY][fromX] = EmptySquare;
9248         board[toY][toX] = BlackPawn;
9249         if(oldEP & EP_BEROLIN_A) {
9250                 captured = board[fromY][fromX-1];
9251                 board[fromY][fromX-1] = EmptySquare;
9252         }else{  captured = board[fromY][fromX+1];
9253                 board[fromY][fromX+1] = EmptySquare;
9254         }
9255     } else {
9256         board[toY][toX] = board[fromY][fromX];
9257         board[fromY][fromX] = EmptySquare;
9258     }
9259   }
9260
9261     if (gameInfo.holdingsWidth != 0) {
9262
9263       /* !!A lot more code needs to be written to support holdings  */
9264       /* [HGM] OK, so I have written it. Holdings are stored in the */
9265       /* penultimate board files, so they are automaticlly stored   */
9266       /* in the game history.                                       */
9267       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9268                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9269         /* Delete from holdings, by decreasing count */
9270         /* and erasing image if necessary            */
9271         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9272         if(p < (int) BlackPawn) { /* white drop */
9273              p -= (int)WhitePawn;
9274                  p = PieceToNumber((ChessSquare)p);
9275              if(p >= gameInfo.holdingsSize) p = 0;
9276              if(--board[p][BOARD_WIDTH-2] <= 0)
9277                   board[p][BOARD_WIDTH-1] = EmptySquare;
9278              if((int)board[p][BOARD_WIDTH-2] < 0)
9279                         board[p][BOARD_WIDTH-2] = 0;
9280         } else {                  /* black drop */
9281              p -= (int)BlackPawn;
9282                  p = PieceToNumber((ChessSquare)p);
9283              if(p >= gameInfo.holdingsSize) p = 0;
9284              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9285                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9286              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9287                         board[BOARD_HEIGHT-1-p][1] = 0;
9288         }
9289       }
9290       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9291           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9292         /* [HGM] holdings: Add to holdings, if holdings exist */
9293         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9294                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9295                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9296         }
9297         p = (int) captured;
9298         if (p >= (int) BlackPawn) {
9299           p -= (int)BlackPawn;
9300           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9301                   /* in Shogi restore piece to its original  first */
9302                   captured = (ChessSquare) (DEMOTED captured);
9303                   p = DEMOTED p;
9304           }
9305           p = PieceToNumber((ChessSquare)p);
9306           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9307           board[p][BOARD_WIDTH-2]++;
9308           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9309         } else {
9310           p -= (int)WhitePawn;
9311           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9312                   captured = (ChessSquare) (DEMOTED captured);
9313                   p = DEMOTED p;
9314           }
9315           p = PieceToNumber((ChessSquare)p);
9316           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9317           board[BOARD_HEIGHT-1-p][1]++;
9318           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9319         }
9320       }
9321     } else if (gameInfo.variant == VariantAtomic) {
9322       if (captured != EmptySquare) {
9323         int y, x;
9324         for (y = toY-1; y <= toY+1; y++) {
9325           for (x = toX-1; x <= toX+1; x++) {
9326             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9327                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9328               board[y][x] = EmptySquare;
9329             }
9330           }
9331         }
9332         board[toY][toX] = EmptySquare;
9333       }
9334     }
9335     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9336         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9337     } else
9338     if(promoChar == '+') {
9339         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9340         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9341     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9342         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9343     }
9344     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9345                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9346         // [HGM] superchess: take promotion piece out of holdings
9347         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9348         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9349             if(!--board[k][BOARD_WIDTH-2])
9350                 board[k][BOARD_WIDTH-1] = EmptySquare;
9351         } else {
9352             if(!--board[BOARD_HEIGHT-1-k][1])
9353                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9354         }
9355     }
9356
9357 }
9358
9359 /* Updates forwardMostMove */
9360 void
9361 MakeMove(fromX, fromY, toX, toY, promoChar)
9362      int fromX, fromY, toX, toY;
9363      int promoChar;
9364 {
9365 //    forwardMostMove++; // [HGM] bare: moved downstream
9366
9367     (void) CoordsToAlgebraic(boards[forwardMostMove],
9368                              PosFlags(forwardMostMove),
9369                              fromY, fromX, toY, toX, promoChar,
9370                              parseList[forwardMostMove]);
9371
9372     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9373         int timeLeft; static int lastLoadFlag=0; int king, piece;
9374         piece = boards[forwardMostMove][fromY][fromX];
9375         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9376         if(gameInfo.variant == VariantKnightmate)
9377             king += (int) WhiteUnicorn - (int) WhiteKing;
9378         if(forwardMostMove == 0) {
9379             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9380                 fprintf(serverMoves, "%s;", UserName());
9381             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9382                 fprintf(serverMoves, "%s;", second.tidy);
9383             fprintf(serverMoves, "%s;", first.tidy);
9384             if(gameMode == MachinePlaysWhite)
9385                 fprintf(serverMoves, "%s;", UserName());
9386             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9387                 fprintf(serverMoves, "%s;", second.tidy);
9388         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9389         lastLoadFlag = loadFlag;
9390         // print base move
9391         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9392         // print castling suffix
9393         if( toY == fromY && piece == king ) {
9394             if(toX-fromX > 1)
9395                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9396             if(fromX-toX >1)
9397                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9398         }
9399         // e.p. suffix
9400         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9401              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9402              boards[forwardMostMove][toY][toX] == EmptySquare
9403              && fromX != toX && fromY != toY)
9404                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9405         // promotion suffix
9406         if(promoChar != NULLCHAR)
9407                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9408         if(!loadFlag) {
9409                 char buf[MOVE_LEN*2], *p; int len;
9410             fprintf(serverMoves, "/%d/%d",
9411                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9412             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9413             else                      timeLeft = blackTimeRemaining/1000;
9414             fprintf(serverMoves, "/%d", timeLeft);
9415                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9416                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9417                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9418             fprintf(serverMoves, "/%s", buf);
9419         }
9420         fflush(serverMoves);
9421     }
9422
9423     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9424         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9425       return;
9426     }
9427     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9428     if (commentList[forwardMostMove+1] != NULL) {
9429         free(commentList[forwardMostMove+1]);
9430         commentList[forwardMostMove+1] = NULL;
9431     }
9432     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9433     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9434     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9435     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9436     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9437     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9438     gameInfo.result = GameUnfinished;
9439     if (gameInfo.resultDetails != NULL) {
9440         free(gameInfo.resultDetails);
9441         gameInfo.resultDetails = NULL;
9442     }
9443     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9444                               moveList[forwardMostMove - 1]);
9445     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9446       case MT_NONE:
9447       case MT_STALEMATE:
9448       default:
9449         break;
9450       case MT_CHECK:
9451         if(gameInfo.variant != VariantShogi)
9452             strcat(parseList[forwardMostMove - 1], "+");
9453         break;
9454       case MT_CHECKMATE:
9455       case MT_STAINMATE:
9456         strcat(parseList[forwardMostMove - 1], "#");
9457         break;
9458     }
9459     if (appData.debugMode) {
9460         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9461     }
9462
9463 }
9464
9465 /* Updates currentMove if not pausing */
9466 void
9467 ShowMove(fromX, fromY, toX, toY)
9468 {
9469     int instant = (gameMode == PlayFromGameFile) ?
9470         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9471     if(appData.noGUI) return;
9472     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9473         if (!instant) {
9474             if (forwardMostMove == currentMove + 1) {
9475                 AnimateMove(boards[forwardMostMove - 1],
9476                             fromX, fromY, toX, toY);
9477             }
9478             if (appData.highlightLastMove) {
9479                 SetHighlights(fromX, fromY, toX, toY);
9480             }
9481         }
9482         currentMove = forwardMostMove;
9483     }
9484
9485     if (instant) return;
9486
9487     DisplayMove(currentMove - 1);
9488     DrawPosition(FALSE, boards[currentMove]);
9489     DisplayBothClocks();
9490     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9491     DisplayBook(currentMove);
9492 }
9493
9494 void SendEgtPath(ChessProgramState *cps)
9495 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9496         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9497
9498         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9499
9500         while(*p) {
9501             char c, *q = name+1, *r, *s;
9502
9503             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9504             while(*p && *p != ',') *q++ = *p++;
9505             *q++ = ':'; *q = 0;
9506             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9507                 strcmp(name, ",nalimov:") == 0 ) {
9508                 // take nalimov path from the menu-changeable option first, if it is defined
9509               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9510                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9511             } else
9512             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9513                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9514                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9515                 s = r = StrStr(s, ":") + 1; // beginning of path info
9516                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9517                 c = *r; *r = 0;             // temporarily null-terminate path info
9518                     *--q = 0;               // strip of trailig ':' from name
9519                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9520                 *r = c;
9521                 SendToProgram(buf,cps);     // send egtbpath command for this format
9522             }
9523             if(*p == ',') p++; // read away comma to position for next format name
9524         }
9525 }
9526
9527 void
9528 InitChessProgram(cps, setup)
9529      ChessProgramState *cps;
9530      int setup; /* [HGM] needed to setup FRC opening position */
9531 {
9532     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9533     if (appData.noChessProgram) return;
9534     hintRequested = FALSE;
9535     bookRequested = FALSE;
9536
9537     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9538     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9539     if(cps->memSize) { /* [HGM] memory */
9540       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9541         SendToProgram(buf, cps);
9542     }
9543     SendEgtPath(cps); /* [HGM] EGT */
9544     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9545       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9546         SendToProgram(buf, cps);
9547     }
9548
9549     SendToProgram(cps->initString, cps);
9550     if (gameInfo.variant != VariantNormal &&
9551         gameInfo.variant != VariantLoadable
9552         /* [HGM] also send variant if board size non-standard */
9553         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9554                                             ) {
9555       char *v = VariantName(gameInfo.variant);
9556       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9557         /* [HGM] in protocol 1 we have to assume all variants valid */
9558         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9559         DisplayFatalError(buf, 0, 1);
9560         return;
9561       }
9562
9563       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9564       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9565       if( gameInfo.variant == VariantXiangqi )
9566            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9567       if( gameInfo.variant == VariantShogi )
9568            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9569       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9570            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9571       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9572           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9573            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9574       if( gameInfo.variant == VariantCourier )
9575            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9576       if( gameInfo.variant == VariantSuper )
9577            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9578       if( gameInfo.variant == VariantGreat )
9579            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9580       if( gameInfo.variant == VariantSChess )
9581            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9582       if( gameInfo.variant == VariantGrand )
9583            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9584
9585       if(overruled) {
9586         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9587                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9588            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9589            if(StrStr(cps->variants, b) == NULL) {
9590                // specific sized variant not known, check if general sizing allowed
9591                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9592                    if(StrStr(cps->variants, "boardsize") == NULL) {
9593                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9594                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9595                        DisplayFatalError(buf, 0, 1);
9596                        return;
9597                    }
9598                    /* [HGM] here we really should compare with the maximum supported board size */
9599                }
9600            }
9601       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9602       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9603       SendToProgram(buf, cps);
9604     }
9605     currentlyInitializedVariant = gameInfo.variant;
9606
9607     /* [HGM] send opening position in FRC to first engine */
9608     if(setup) {
9609           SendToProgram("force\n", cps);
9610           SendBoard(cps, 0);
9611           /* engine is now in force mode! Set flag to wake it up after first move. */
9612           setboardSpoiledMachineBlack = 1;
9613     }
9614
9615     if (cps->sendICS) {
9616       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9617       SendToProgram(buf, cps);
9618     }
9619     cps->maybeThinking = FALSE;
9620     cps->offeredDraw = 0;
9621     if (!appData.icsActive) {
9622         SendTimeControl(cps, movesPerSession, timeControl,
9623                         timeIncrement, appData.searchDepth,
9624                         searchTime);
9625     }
9626     if (appData.showThinking
9627         // [HGM] thinking: four options require thinking output to be sent
9628         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9629                                 ) {
9630         SendToProgram("post\n", cps);
9631     }
9632     SendToProgram("hard\n", cps);
9633     if (!appData.ponderNextMove) {
9634         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9635            it without being sure what state we are in first.  "hard"
9636            is not a toggle, so that one is OK.
9637          */
9638         SendToProgram("easy\n", cps);
9639     }
9640     if (cps->usePing) {
9641       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9642       SendToProgram(buf, cps);
9643     }
9644     cps->initDone = TRUE;
9645     ClearEngineOutputPane(cps == &second);
9646 }
9647
9648
9649 void
9650 StartChessProgram(cps)
9651      ChessProgramState *cps;
9652 {
9653     char buf[MSG_SIZ];
9654     int err;
9655
9656     if (appData.noChessProgram) return;
9657     cps->initDone = FALSE;
9658
9659     if (strcmp(cps->host, "localhost") == 0) {
9660         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9661     } else if (*appData.remoteShell == NULLCHAR) {
9662         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9663     } else {
9664         if (*appData.remoteUser == NULLCHAR) {
9665           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9666                     cps->program);
9667         } else {
9668           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9669                     cps->host, appData.remoteUser, cps->program);
9670         }
9671         err = StartChildProcess(buf, "", &cps->pr);
9672     }
9673
9674     if (err != 0) {
9675       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9676         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9677         if(cps != &first) return;
9678         appData.noChessProgram = TRUE;
9679         ThawUI();
9680         SetNCPMode();
9681 //      DisplayFatalError(buf, err, 1);
9682 //      cps->pr = NoProc;
9683 //      cps->isr = NULL;
9684         return;
9685     }
9686
9687     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9688     if (cps->protocolVersion > 1) {
9689       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9690       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9691       cps->comboCnt = 0;  //                and values of combo boxes
9692       SendToProgram(buf, cps);
9693     } else {
9694       SendToProgram("xboard\n", cps);
9695     }
9696 }
9697
9698 void
9699 TwoMachinesEventIfReady P((void))
9700 {
9701   static int curMess = 0;
9702   if (first.lastPing != first.lastPong) {
9703     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9704     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9705     return;
9706   }
9707   if (second.lastPing != second.lastPong) {
9708     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9709     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9710     return;
9711   }
9712   DisplayMessage("", ""); curMess = 0;
9713   ThawUI();
9714   TwoMachinesEvent();
9715 }
9716
9717 char *
9718 MakeName(char *template)
9719 {
9720     time_t clock;
9721     struct tm *tm;
9722     static char buf[MSG_SIZ];
9723     char *p = buf;
9724     int i;
9725
9726     clock = time((time_t *)NULL);
9727     tm = localtime(&clock);
9728
9729     while(*p++ = *template++) if(p[-1] == '%') {
9730         switch(*template++) {
9731           case 0:   *p = 0; return buf;
9732           case 'Y': i = tm->tm_year+1900; break;
9733           case 'y': i = tm->tm_year-100; break;
9734           case 'M': i = tm->tm_mon+1; break;
9735           case 'd': i = tm->tm_mday; break;
9736           case 'h': i = tm->tm_hour; break;
9737           case 'm': i = tm->tm_min; break;
9738           case 's': i = tm->tm_sec; break;
9739           default:  i = 0;
9740         }
9741         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9742     }
9743     return buf;
9744 }
9745
9746 int
9747 CountPlayers(char *p)
9748 {
9749     int n = 0;
9750     while(p = strchr(p, '\n')) p++, n++; // count participants
9751     return n;
9752 }
9753
9754 FILE *
9755 WriteTourneyFile(char *results, FILE *f)
9756 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9757     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9758     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9759         // create a file with tournament description
9760         fprintf(f, "-participants {%s}\n", appData.participants);
9761         fprintf(f, "-seedBase %d\n", appData.seedBase);
9762         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9763         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9764         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9765         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9766         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9767         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9768         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9769         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9770         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9771         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9772         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9773         if(searchTime > 0)
9774                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9775         else {
9776                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9777                 fprintf(f, "-tc %s\n", appData.timeControl);
9778                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9779         }
9780         fprintf(f, "-results \"%s\"\n", results);
9781     }
9782     return f;
9783 }
9784
9785 #define MAXENGINES 1000
9786 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9787
9788 void Substitute(char *participants, int expunge)
9789 {
9790     int i, changed, changes=0, nPlayers=0;
9791     char *p, *q, *r, buf[MSG_SIZ];
9792     if(participants == NULL) return;
9793     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9794     r = p = participants; q = appData.participants;
9795     while(*p && *p == *q) {
9796         if(*p == '\n') r = p+1, nPlayers++;
9797         p++; q++;
9798     }
9799     if(*p) { // difference
9800         while(*p && *p++ != '\n');
9801         while(*q && *q++ != '\n');
9802       changed = nPlayers;
9803         changes = 1 + (strcmp(p, q) != 0);
9804     }
9805     if(changes == 1) { // a single engine mnemonic was changed
9806         q = r; while(*q) nPlayers += (*q++ == '\n');
9807         p = buf; while(*r && (*p = *r++) != '\n') p++;
9808         *p = NULLCHAR;
9809         NamesToList(firstChessProgramNames, command, mnemonic);
9810         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9811         if(mnemonic[i]) { // The substitute is valid
9812             FILE *f;
9813             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9814                 flock(fileno(f), LOCK_EX);
9815                 ParseArgsFromFile(f);
9816                 fseek(f, 0, SEEK_SET);
9817                 FREE(appData.participants); appData.participants = participants;
9818                 if(expunge) { // erase results of replaced engine
9819                     int len = strlen(appData.results), w, b, dummy;
9820                     for(i=0; i<len; i++) {
9821                         Pairing(i, nPlayers, &w, &b, &dummy);
9822                         if((w == changed || b == changed) && appData.results[i] == '*') {
9823                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9824                             fclose(f);
9825                             return;
9826                         }
9827                     }
9828                     for(i=0; i<len; i++) {
9829                         Pairing(i, nPlayers, &w, &b, &dummy);
9830                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9831                     }
9832                 }
9833                 WriteTourneyFile(appData.results, f);
9834                 fclose(f); // release lock
9835                 return;
9836             }
9837         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9838     }
9839     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9840     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9841     free(participants);
9842     return;
9843 }
9844
9845 int
9846 CreateTourney(char *name)
9847 {
9848         FILE *f;
9849         if(matchMode && strcmp(name, appData.tourneyFile)) {
9850              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9851         }
9852         if(name[0] == NULLCHAR) {
9853             if(appData.participants[0])
9854                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9855             return 0;
9856         }
9857         f = fopen(name, "r");
9858         if(f) { // file exists
9859             ASSIGN(appData.tourneyFile, name);
9860             ParseArgsFromFile(f); // parse it
9861         } else {
9862             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9863             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9864                 DisplayError(_("Not enough participants"), 0);
9865                 return 0;
9866             }
9867             ASSIGN(appData.tourneyFile, name);
9868             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9869             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9870         }
9871         fclose(f);
9872         appData.noChessProgram = FALSE;
9873         appData.clockMode = TRUE;
9874         SetGNUMode();
9875         return 1;
9876 }
9877
9878 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9879 {
9880     char buf[MSG_SIZ], *p, *q;
9881     int i=1;
9882     while(*names) {
9883         p = names; q = buf;
9884         while(*p && *p != '\n') *q++ = *p++;
9885         *q = 0;
9886         if(engineList[i]) free(engineList[i]);
9887         engineList[i] = strdup(buf);
9888         if(*p == '\n') p++;
9889         TidyProgramName(engineList[i], "localhost", buf);
9890         if(engineMnemonic[i]) free(engineMnemonic[i]);
9891         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9892             strcat(buf, " (");
9893             sscanf(q + 8, "%s", buf + strlen(buf));
9894             strcat(buf, ")");
9895         }
9896         engineMnemonic[i] = strdup(buf);
9897         names = p; i++;
9898       if(i > MAXENGINES - 2) break;
9899     }
9900     engineList[i] = engineMnemonic[i] = NULL;
9901 }
9902
9903 // following implemented as macro to avoid type limitations
9904 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9905
9906 void SwapEngines(int n)
9907 {   // swap settings for first engine and other engine (so far only some selected options)
9908     int h;
9909     char *p;
9910     if(n == 0) return;
9911     SWAP(directory, p)
9912     SWAP(chessProgram, p)
9913     SWAP(isUCI, h)
9914     SWAP(hasOwnBookUCI, h)
9915     SWAP(protocolVersion, h)
9916     SWAP(reuse, h)
9917     SWAP(scoreIsAbsolute, h)
9918     SWAP(timeOdds, h)
9919     SWAP(logo, p)
9920     SWAP(pgnName, p)
9921     SWAP(pvSAN, h)
9922 }
9923
9924 void
9925 SetPlayer(int player)
9926 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9927     int i;
9928     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9929     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9930     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9931     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9932     if(mnemonic[i]) {
9933         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9934         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9935         ParseArgsFromString(buf);
9936     }
9937     free(engineName);
9938 }
9939
9940 int
9941 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9942 {   // determine players from game number
9943     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9944
9945     if(appData.tourneyType == 0) {
9946         roundsPerCycle = (nPlayers - 1) | 1;
9947         pairingsPerRound = nPlayers / 2;
9948     } else if(appData.tourneyType > 0) {
9949         roundsPerCycle = nPlayers - appData.tourneyType;
9950         pairingsPerRound = appData.tourneyType;
9951     }
9952     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9953     gamesPerCycle = gamesPerRound * roundsPerCycle;
9954     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9955     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9956     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9957     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9958     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9959     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9960
9961     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9962     if(appData.roundSync) *syncInterval = gamesPerRound;
9963
9964     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9965
9966     if(appData.tourneyType == 0) {
9967         if(curPairing == (nPlayers-1)/2 ) {
9968             *whitePlayer = curRound;
9969             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9970         } else {
9971             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9972             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9973             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9974             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9975         }
9976     } else if(appData.tourneyType > 0) {
9977         *whitePlayer = curPairing;
9978         *blackPlayer = curRound + appData.tourneyType;
9979     }
9980
9981     // take care of white/black alternation per round. 
9982     // For cycles and games this is already taken care of by default, derived from matchGame!
9983     return curRound & 1;
9984 }
9985
9986 int
9987 NextTourneyGame(int nr, int *swapColors)
9988 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9989     char *p, *q;
9990     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9991     FILE *tf;
9992     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9993     tf = fopen(appData.tourneyFile, "r");
9994     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9995     ParseArgsFromFile(tf); fclose(tf);
9996     InitTimeControls(); // TC might be altered from tourney file
9997
9998     nPlayers = CountPlayers(appData.participants); // count participants
9999     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10000     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10001
10002     if(syncInterval) {
10003         p = q = appData.results;
10004         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10005         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10006             DisplayMessage(_("Waiting for other game(s)"),"");
10007             waitingForGame = TRUE;
10008             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10009             return 0;
10010         }
10011         waitingForGame = FALSE;
10012     }
10013
10014     if(appData.tourneyType < 0) {
10015         if(nr>=0 && !pairingReceived) {
10016             char buf[1<<16];
10017             if(pairing.pr == NoProc) {
10018                 if(!appData.pairingEngine[0]) {
10019                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10020                     return 0;
10021                 }
10022                 StartChessProgram(&pairing); // starts the pairing engine
10023             }
10024             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10025             SendToProgram(buf, &pairing);
10026             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10027             SendToProgram(buf, &pairing);
10028             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10029         }
10030         pairingReceived = 0;                              // ... so we continue here 
10031         *swapColors = 0;
10032         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10033         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10034         matchGame = 1; roundNr = nr / syncInterval + 1;
10035     }
10036
10037     if(first.pr != NoProc) return 1; // engines already loaded
10038
10039     // redefine engines, engine dir, etc.
10040     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10041     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10042     SwapEngines(1);
10043     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10044     SwapEngines(1);         // and make that valid for second engine by swapping
10045     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10046     InitEngine(&second, 1);
10047     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10048     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10049     return 1;
10050 }
10051
10052 void
10053 NextMatchGame()
10054 {   // performs game initialization that does not invoke engines, and then tries to start the game
10055     int res, firstWhite, swapColors = 0;
10056     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10057     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10058     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10059     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10060     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10061     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10062     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10063     Reset(FALSE, first.pr != NoProc);
10064     res = LoadGameOrPosition(matchGame); // setup game
10065     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10066     if(!res) return; // 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 && !appData.noChessProgram) {
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     CopyBoard(boards[0], initial_position);
11880     if (blackPlaysFirst) {
11881         currentMove = forwardMostMove = backwardMostMove = 1;
11882         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11883         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11884         CopyBoard(boards[1], initial_position);
11885         DisplayMessage("", _("Black to play"));
11886     } else {
11887         currentMove = forwardMostMove = backwardMostMove = 0;
11888         DisplayMessage("", _("White to play"));
11889     }
11890     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11891     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
11892         SendToProgram("force\n", &first);
11893         SendBoard(&first, forwardMostMove);
11894     }
11895     if (appData.debugMode) {
11896 int i, j;
11897   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11898   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11899         fprintf(debugFP, "Load Position\n");
11900     }
11901
11902     if (positionNumber > 1) {
11903       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11904         DisplayTitle(line);
11905     } else {
11906         DisplayTitle(title);
11907     }
11908     gameMode = EditGame;
11909     ModeHighlight();
11910     ResetClocks();
11911     timeRemaining[0][1] = whiteTimeRemaining;
11912     timeRemaining[1][1] = blackTimeRemaining;
11913     DrawPosition(FALSE, boards[currentMove]);
11914
11915     return TRUE;
11916 }
11917
11918
11919 void
11920 CopyPlayerNameIntoFileName(dest, src)
11921      char **dest, *src;
11922 {
11923     while (*src != NULLCHAR && *src != ',') {
11924         if (*src == ' ') {
11925             *(*dest)++ = '_';
11926             src++;
11927         } else {
11928             *(*dest)++ = *src++;
11929         }
11930     }
11931 }
11932
11933 char *DefaultFileName(ext)
11934      char *ext;
11935 {
11936     static char def[MSG_SIZ];
11937     char *p;
11938
11939     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11940         p = def;
11941         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11942         *p++ = '-';
11943         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11944         *p++ = '.';
11945         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11946     } else {
11947         def[0] = NULLCHAR;
11948     }
11949     return def;
11950 }
11951
11952 /* Save the current game to the given file */
11953 int
11954 SaveGameToFile(filename, append)
11955      char *filename;
11956      int append;
11957 {
11958     FILE *f;
11959     char buf[MSG_SIZ];
11960     int result, i, t,tot=0;
11961
11962     if (strcmp(filename, "-") == 0) {
11963         return SaveGame(stdout, 0, NULL);
11964     } else {
11965         for(i=0; i<10; i++) { // upto 10 tries
11966              f = fopen(filename, append ? "a" : "w");
11967              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
11968              if(f || errno != 13) break;
11969              DoSleep(t = 5 + random()%11); // wait 5-15 msec
11970              tot += t;
11971         }
11972         if (f == NULL) {
11973             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11974             DisplayError(buf, errno);
11975             return FALSE;
11976         } else {
11977             safeStrCpy(buf, lastMsg, MSG_SIZ);
11978             DisplayMessage(_("Waiting for access to save file"), "");
11979             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11980             DisplayMessage(_("Saving game"), "");
11981             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11982             result = SaveGame(f, 0, NULL);
11983             DisplayMessage(buf, "");
11984             return result;
11985         }
11986     }
11987 }
11988
11989 char *
11990 SavePart(str)
11991      char *str;
11992 {
11993     static char buf[MSG_SIZ];
11994     char *p;
11995
11996     p = strchr(str, ' ');
11997     if (p == NULL) return str;
11998     strncpy(buf, str, p - str);
11999     buf[p - str] = NULLCHAR;
12000     return buf;
12001 }
12002
12003 #define PGN_MAX_LINE 75
12004
12005 #define PGN_SIDE_WHITE  0
12006 #define PGN_SIDE_BLACK  1
12007
12008 /* [AS] */
12009 static int FindFirstMoveOutOfBook( int side )
12010 {
12011     int result = -1;
12012
12013     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12014         int index = backwardMostMove;
12015         int has_book_hit = 0;
12016
12017         if( (index % 2) != side ) {
12018             index++;
12019         }
12020
12021         while( index < forwardMostMove ) {
12022             /* Check to see if engine is in book */
12023             int depth = pvInfoList[index].depth;
12024             int score = pvInfoList[index].score;
12025             int in_book = 0;
12026
12027             if( depth <= 2 ) {
12028                 in_book = 1;
12029             }
12030             else if( score == 0 && depth == 63 ) {
12031                 in_book = 1; /* Zappa */
12032             }
12033             else if( score == 2 && depth == 99 ) {
12034                 in_book = 1; /* Abrok */
12035             }
12036
12037             has_book_hit += in_book;
12038
12039             if( ! in_book ) {
12040                 result = index;
12041
12042                 break;
12043             }
12044
12045             index += 2;
12046         }
12047     }
12048
12049     return result;
12050 }
12051
12052 /* [AS] */
12053 void GetOutOfBookInfo( char * buf )
12054 {
12055     int oob[2];
12056     int i;
12057     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12058
12059     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12060     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12061
12062     *buf = '\0';
12063
12064     if( oob[0] >= 0 || oob[1] >= 0 ) {
12065         for( i=0; i<2; i++ ) {
12066             int idx = oob[i];
12067
12068             if( idx >= 0 ) {
12069                 if( i > 0 && oob[0] >= 0 ) {
12070                     strcat( buf, "   " );
12071                 }
12072
12073                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12074                 sprintf( buf+strlen(buf), "%s%.2f",
12075                     pvInfoList[idx].score >= 0 ? "+" : "",
12076                     pvInfoList[idx].score / 100.0 );
12077             }
12078         }
12079     }
12080 }
12081
12082 /* Save game in PGN style and close the file */
12083 int
12084 SaveGamePGN(f)
12085      FILE *f;
12086 {
12087     int i, offset, linelen, newblock;
12088     time_t tm;
12089 //    char *movetext;
12090     char numtext[32];
12091     int movelen, numlen, blank;
12092     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12093
12094     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12095
12096     tm = time((time_t *) NULL);
12097
12098     PrintPGNTags(f, &gameInfo);
12099
12100     if (backwardMostMove > 0 || startedFromSetupPosition) {
12101         char *fen = PositionToFEN(backwardMostMove, NULL);
12102         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12103         fprintf(f, "\n{--------------\n");
12104         PrintPosition(f, backwardMostMove);
12105         fprintf(f, "--------------}\n");
12106         free(fen);
12107     }
12108     else {
12109         /* [AS] Out of book annotation */
12110         if( appData.saveOutOfBookInfo ) {
12111             char buf[64];
12112
12113             GetOutOfBookInfo( buf );
12114
12115             if( buf[0] != '\0' ) {
12116                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12117             }
12118         }
12119
12120         fprintf(f, "\n");
12121     }
12122
12123     i = backwardMostMove;
12124     linelen = 0;
12125     newblock = TRUE;
12126
12127     while (i < forwardMostMove) {
12128         /* Print comments preceding this move */
12129         if (commentList[i] != NULL) {
12130             if (linelen > 0) fprintf(f, "\n");
12131             fprintf(f, "%s", commentList[i]);
12132             linelen = 0;
12133             newblock = TRUE;
12134         }
12135
12136         /* Format move number */
12137         if ((i % 2) == 0)
12138           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12139         else
12140           if (newblock)
12141             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12142           else
12143             numtext[0] = NULLCHAR;
12144
12145         numlen = strlen(numtext);
12146         newblock = FALSE;
12147
12148         /* Print move number */
12149         blank = linelen > 0 && numlen > 0;
12150         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12151             fprintf(f, "\n");
12152             linelen = 0;
12153             blank = 0;
12154         }
12155         if (blank) {
12156             fprintf(f, " ");
12157             linelen++;
12158         }
12159         fprintf(f, "%s", numtext);
12160         linelen += numlen;
12161
12162         /* Get move */
12163         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12164         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12165
12166         /* Print move */
12167         blank = linelen > 0 && movelen > 0;
12168         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12169             fprintf(f, "\n");
12170             linelen = 0;
12171             blank = 0;
12172         }
12173         if (blank) {
12174             fprintf(f, " ");
12175             linelen++;
12176         }
12177         fprintf(f, "%s", move_buffer);
12178         linelen += movelen;
12179
12180         /* [AS] Add PV info if present */
12181         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12182             /* [HGM] add time */
12183             char buf[MSG_SIZ]; int seconds;
12184
12185             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12186
12187             if( seconds <= 0)
12188               buf[0] = 0;
12189             else
12190               if( seconds < 30 )
12191                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12192               else
12193                 {
12194                   seconds = (seconds + 4)/10; // round to full seconds
12195                   if( seconds < 60 )
12196                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12197                   else
12198                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12199                 }
12200
12201             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12202                       pvInfoList[i].score >= 0 ? "+" : "",
12203                       pvInfoList[i].score / 100.0,
12204                       pvInfoList[i].depth,
12205                       buf );
12206
12207             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12208
12209             /* Print score/depth */
12210             blank = linelen > 0 && movelen > 0;
12211             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12212                 fprintf(f, "\n");
12213                 linelen = 0;
12214                 blank = 0;
12215             }
12216             if (blank) {
12217                 fprintf(f, " ");
12218                 linelen++;
12219             }
12220             fprintf(f, "%s", move_buffer);
12221             linelen += movelen;
12222         }
12223
12224         i++;
12225     }
12226
12227     /* Start a new line */
12228     if (linelen > 0) fprintf(f, "\n");
12229
12230     /* Print comments after last move */
12231     if (commentList[i] != NULL) {
12232         fprintf(f, "%s\n", commentList[i]);
12233     }
12234
12235     /* Print result */
12236     if (gameInfo.resultDetails != NULL &&
12237         gameInfo.resultDetails[0] != NULLCHAR) {
12238         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12239                 PGNResult(gameInfo.result));
12240     } else {
12241         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12242     }
12243
12244     fclose(f);
12245     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12246     return TRUE;
12247 }
12248
12249 /* Save game in old style and close the file */
12250 int
12251 SaveGameOldStyle(f)
12252      FILE *f;
12253 {
12254     int i, offset;
12255     time_t tm;
12256
12257     tm = time((time_t *) NULL);
12258
12259     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12260     PrintOpponents(f);
12261
12262     if (backwardMostMove > 0 || startedFromSetupPosition) {
12263         fprintf(f, "\n[--------------\n");
12264         PrintPosition(f, backwardMostMove);
12265         fprintf(f, "--------------]\n");
12266     } else {
12267         fprintf(f, "\n");
12268     }
12269
12270     i = backwardMostMove;
12271     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12272
12273     while (i < forwardMostMove) {
12274         if (commentList[i] != NULL) {
12275             fprintf(f, "[%s]\n", commentList[i]);
12276         }
12277
12278         if ((i % 2) == 1) {
12279             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12280             i++;
12281         } else {
12282             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12283             i++;
12284             if (commentList[i] != NULL) {
12285                 fprintf(f, "\n");
12286                 continue;
12287             }
12288             if (i >= forwardMostMove) {
12289                 fprintf(f, "\n");
12290                 break;
12291             }
12292             fprintf(f, "%s\n", parseList[i]);
12293             i++;
12294         }
12295     }
12296
12297     if (commentList[i] != NULL) {
12298         fprintf(f, "[%s]\n", commentList[i]);
12299     }
12300
12301     /* This isn't really the old style, but it's close enough */
12302     if (gameInfo.resultDetails != NULL &&
12303         gameInfo.resultDetails[0] != NULLCHAR) {
12304         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12305                 gameInfo.resultDetails);
12306     } else {
12307         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12308     }
12309
12310     fclose(f);
12311     return TRUE;
12312 }
12313
12314 /* Save the current game to open file f and close the file */
12315 int
12316 SaveGame(f, dummy, dummy2)
12317      FILE *f;
12318      int dummy;
12319      char *dummy2;
12320 {
12321     if (gameMode == EditPosition) EditPositionDone(TRUE);
12322     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12323     if (appData.oldSaveStyle)
12324       return SaveGameOldStyle(f);
12325     else
12326       return SaveGamePGN(f);
12327 }
12328
12329 /* Save the current position to the given file */
12330 int
12331 SavePositionToFile(filename)
12332      char *filename;
12333 {
12334     FILE *f;
12335     char buf[MSG_SIZ];
12336
12337     if (strcmp(filename, "-") == 0) {
12338         return SavePosition(stdout, 0, NULL);
12339     } else {
12340         f = fopen(filename, "a");
12341         if (f == NULL) {
12342             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12343             DisplayError(buf, errno);
12344             return FALSE;
12345         } else {
12346             safeStrCpy(buf, lastMsg, MSG_SIZ);
12347             DisplayMessage(_("Waiting for access to save file"), "");
12348             flock(fileno(f), LOCK_EX); // [HGM] lock
12349             DisplayMessage(_("Saving position"), "");
12350             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12351             SavePosition(f, 0, NULL);
12352             DisplayMessage(buf, "");
12353             return TRUE;
12354         }
12355     }
12356 }
12357
12358 /* Save the current position to the given open file and close the file */
12359 int
12360 SavePosition(f, dummy, dummy2)
12361      FILE *f;
12362      int dummy;
12363      char *dummy2;
12364 {
12365     time_t tm;
12366     char *fen;
12367
12368     if (gameMode == EditPosition) EditPositionDone(TRUE);
12369     if (appData.oldSaveStyle) {
12370         tm = time((time_t *) NULL);
12371
12372         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12373         PrintOpponents(f);
12374         fprintf(f, "[--------------\n");
12375         PrintPosition(f, currentMove);
12376         fprintf(f, "--------------]\n");
12377     } else {
12378         fen = PositionToFEN(currentMove, NULL);
12379         fprintf(f, "%s\n", fen);
12380         free(fen);
12381     }
12382     fclose(f);
12383     return TRUE;
12384 }
12385
12386 void
12387 ReloadCmailMsgEvent(unregister)
12388      int unregister;
12389 {
12390 #if !WIN32
12391     static char *inFilename = NULL;
12392     static char *outFilename;
12393     int i;
12394     struct stat inbuf, outbuf;
12395     int status;
12396
12397     /* Any registered moves are unregistered if unregister is set, */
12398     /* i.e. invoked by the signal handler */
12399     if (unregister) {
12400         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12401             cmailMoveRegistered[i] = FALSE;
12402             if (cmailCommentList[i] != NULL) {
12403                 free(cmailCommentList[i]);
12404                 cmailCommentList[i] = NULL;
12405             }
12406         }
12407         nCmailMovesRegistered = 0;
12408     }
12409
12410     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12411         cmailResult[i] = CMAIL_NOT_RESULT;
12412     }
12413     nCmailResults = 0;
12414
12415     if (inFilename == NULL) {
12416         /* Because the filenames are static they only get malloced once  */
12417         /* and they never get freed                                      */
12418         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12419         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12420
12421         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12422         sprintf(outFilename, "%s.out", appData.cmailGameName);
12423     }
12424
12425     status = stat(outFilename, &outbuf);
12426     if (status < 0) {
12427         cmailMailedMove = FALSE;
12428     } else {
12429         status = stat(inFilename, &inbuf);
12430         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12431     }
12432
12433     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12434        counts the games, notes how each one terminated, etc.
12435
12436        It would be nice to remove this kludge and instead gather all
12437        the information while building the game list.  (And to keep it
12438        in the game list nodes instead of having a bunch of fixed-size
12439        parallel arrays.)  Note this will require getting each game's
12440        termination from the PGN tags, as the game list builder does
12441        not process the game moves.  --mann
12442        */
12443     cmailMsgLoaded = TRUE;
12444     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12445
12446     /* Load first game in the file or popup game menu */
12447     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12448
12449 #endif /* !WIN32 */
12450     return;
12451 }
12452
12453 int
12454 RegisterMove()
12455 {
12456     FILE *f;
12457     char string[MSG_SIZ];
12458
12459     if (   cmailMailedMove
12460         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12461         return TRUE;            /* Allow free viewing  */
12462     }
12463
12464     /* Unregister move to ensure that we don't leave RegisterMove        */
12465     /* with the move registered when the conditions for registering no   */
12466     /* longer hold                                                       */
12467     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12468         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12469         nCmailMovesRegistered --;
12470
12471         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12472           {
12473               free(cmailCommentList[lastLoadGameNumber - 1]);
12474               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12475           }
12476     }
12477
12478     if (cmailOldMove == -1) {
12479         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12480         return FALSE;
12481     }
12482
12483     if (currentMove > cmailOldMove + 1) {
12484         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12485         return FALSE;
12486     }
12487
12488     if (currentMove < cmailOldMove) {
12489         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12490         return FALSE;
12491     }
12492
12493     if (forwardMostMove > currentMove) {
12494         /* Silently truncate extra moves */
12495         TruncateGame();
12496     }
12497
12498     if (   (currentMove == cmailOldMove + 1)
12499         || (   (currentMove == cmailOldMove)
12500             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12501                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12502         if (gameInfo.result != GameUnfinished) {
12503             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12504         }
12505
12506         if (commentList[currentMove] != NULL) {
12507             cmailCommentList[lastLoadGameNumber - 1]
12508               = StrSave(commentList[currentMove]);
12509         }
12510         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12511
12512         if (appData.debugMode)
12513           fprintf(debugFP, "Saving %s for game %d\n",
12514                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12515
12516         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12517
12518         f = fopen(string, "w");
12519         if (appData.oldSaveStyle) {
12520             SaveGameOldStyle(f); /* also closes the file */
12521
12522             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12523             f = fopen(string, "w");
12524             SavePosition(f, 0, NULL); /* also closes the file */
12525         } else {
12526             fprintf(f, "{--------------\n");
12527             PrintPosition(f, currentMove);
12528             fprintf(f, "--------------}\n\n");
12529
12530             SaveGame(f, 0, NULL); /* also closes the file*/
12531         }
12532
12533         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12534         nCmailMovesRegistered ++;
12535     } else if (nCmailGames == 1) {
12536         DisplayError(_("You have not made a move yet"), 0);
12537         return FALSE;
12538     }
12539
12540     return TRUE;
12541 }
12542
12543 void
12544 MailMoveEvent()
12545 {
12546 #if !WIN32
12547     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12548     FILE *commandOutput;
12549     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12550     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12551     int nBuffers;
12552     int i;
12553     int archived;
12554     char *arcDir;
12555
12556     if (! cmailMsgLoaded) {
12557         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12558         return;
12559     }
12560
12561     if (nCmailGames == nCmailResults) {
12562         DisplayError(_("No unfinished games"), 0);
12563         return;
12564     }
12565
12566 #if CMAIL_PROHIBIT_REMAIL
12567     if (cmailMailedMove) {
12568       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);
12569         DisplayError(msg, 0);
12570         return;
12571     }
12572 #endif
12573
12574     if (! (cmailMailedMove || RegisterMove())) return;
12575
12576     if (   cmailMailedMove
12577         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12578       snprintf(string, MSG_SIZ, partCommandString,
12579                appData.debugMode ? " -v" : "", appData.cmailGameName);
12580         commandOutput = popen(string, "r");
12581
12582         if (commandOutput == NULL) {
12583             DisplayError(_("Failed to invoke cmail"), 0);
12584         } else {
12585             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12586                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12587             }
12588             if (nBuffers > 1) {
12589                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12590                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12591                 nBytes = MSG_SIZ - 1;
12592             } else {
12593                 (void) memcpy(msg, buffer, nBytes);
12594             }
12595             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12596
12597             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12598                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12599
12600                 archived = TRUE;
12601                 for (i = 0; i < nCmailGames; i ++) {
12602                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12603                         archived = FALSE;
12604                     }
12605                 }
12606                 if (   archived
12607                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12608                         != NULL)) {
12609                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12610                            arcDir,
12611                            appData.cmailGameName,
12612                            gameInfo.date);
12613                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12614                     cmailMsgLoaded = FALSE;
12615                 }
12616             }
12617
12618             DisplayInformation(msg);
12619             pclose(commandOutput);
12620         }
12621     } else {
12622         if ((*cmailMsg) != '\0') {
12623             DisplayInformation(cmailMsg);
12624         }
12625     }
12626
12627     return;
12628 #endif /* !WIN32 */
12629 }
12630
12631 char *
12632 CmailMsg()
12633 {
12634 #if WIN32
12635     return NULL;
12636 #else
12637     int  prependComma = 0;
12638     char number[5];
12639     char string[MSG_SIZ];       /* Space for game-list */
12640     int  i;
12641
12642     if (!cmailMsgLoaded) return "";
12643
12644     if (cmailMailedMove) {
12645       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12646     } else {
12647         /* Create a list of games left */
12648       snprintf(string, MSG_SIZ, "[");
12649         for (i = 0; i < nCmailGames; i ++) {
12650             if (! (   cmailMoveRegistered[i]
12651                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12652                 if (prependComma) {
12653                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12654                 } else {
12655                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12656                     prependComma = 1;
12657                 }
12658
12659                 strcat(string, number);
12660             }
12661         }
12662         strcat(string, "]");
12663
12664         if (nCmailMovesRegistered + nCmailResults == 0) {
12665             switch (nCmailGames) {
12666               case 1:
12667                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12668                 break;
12669
12670               case 2:
12671                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12672                 break;
12673
12674               default:
12675                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12676                          nCmailGames);
12677                 break;
12678             }
12679         } else {
12680             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12681               case 1:
12682                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12683                          string);
12684                 break;
12685
12686               case 0:
12687                 if (nCmailResults == nCmailGames) {
12688                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12689                 } else {
12690                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12691                 }
12692                 break;
12693
12694               default:
12695                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12696                          string);
12697             }
12698         }
12699     }
12700     return cmailMsg;
12701 #endif /* WIN32 */
12702 }
12703
12704 void
12705 ResetGameEvent()
12706 {
12707     if (gameMode == Training)
12708       SetTrainingModeOff();
12709
12710     Reset(TRUE, TRUE);
12711     cmailMsgLoaded = FALSE;
12712     if (appData.icsActive) {
12713       SendToICS(ics_prefix);
12714       SendToICS("refresh\n");
12715     }
12716 }
12717
12718 void
12719 ExitEvent(status)
12720      int status;
12721 {
12722     exiting++;
12723     if (exiting > 2) {
12724       /* Give up on clean exit */
12725       exit(status);
12726     }
12727     if (exiting > 1) {
12728       /* Keep trying for clean exit */
12729       return;
12730     }
12731
12732     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12733
12734     if (telnetISR != NULL) {
12735       RemoveInputSource(telnetISR);
12736     }
12737     if (icsPR != NoProc) {
12738       DestroyChildProcess(icsPR, TRUE);
12739     }
12740
12741     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12742     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12743
12744     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12745     /* make sure this other one finishes before killing it!                  */
12746     if(endingGame) { int count = 0;
12747         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12748         while(endingGame && count++ < 10) DoSleep(1);
12749         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12750     }
12751
12752     /* Kill off chess programs */
12753     if (first.pr != NoProc) {
12754         ExitAnalyzeMode();
12755
12756         DoSleep( appData.delayBeforeQuit );
12757         SendToProgram("quit\n", &first);
12758         DoSleep( appData.delayAfterQuit );
12759         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12760     }
12761     if (second.pr != NoProc) {
12762         DoSleep( appData.delayBeforeQuit );
12763         SendToProgram("quit\n", &second);
12764         DoSleep( appData.delayAfterQuit );
12765         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12766     }
12767     if (first.isr != NULL) {
12768         RemoveInputSource(first.isr);
12769     }
12770     if (second.isr != NULL) {
12771         RemoveInputSource(second.isr);
12772     }
12773
12774     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12775     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12776
12777     ShutDownFrontEnd();
12778     exit(status);
12779 }
12780
12781 void
12782 PauseEvent()
12783 {
12784     if (appData.debugMode)
12785         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12786     if (pausing) {
12787         pausing = FALSE;
12788         ModeHighlight();
12789         if (gameMode == MachinePlaysWhite ||
12790             gameMode == MachinePlaysBlack) {
12791             StartClocks();
12792         } else {
12793             DisplayBothClocks();
12794         }
12795         if (gameMode == PlayFromGameFile) {
12796             if (appData.timeDelay >= 0)
12797                 AutoPlayGameLoop();
12798         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12799             Reset(FALSE, TRUE);
12800             SendToICS(ics_prefix);
12801             SendToICS("refresh\n");
12802         } else if (currentMove < forwardMostMove) {
12803             ForwardInner(forwardMostMove);
12804         }
12805         pauseExamInvalid = FALSE;
12806     } else {
12807         switch (gameMode) {
12808           default:
12809             return;
12810           case IcsExamining:
12811             pauseExamForwardMostMove = forwardMostMove;
12812             pauseExamInvalid = FALSE;
12813             /* fall through */
12814           case IcsObserving:
12815           case IcsPlayingWhite:
12816           case IcsPlayingBlack:
12817             pausing = TRUE;
12818             ModeHighlight();
12819             return;
12820           case PlayFromGameFile:
12821             (void) StopLoadGameTimer();
12822             pausing = TRUE;
12823             ModeHighlight();
12824             break;
12825           case BeginningOfGame:
12826             if (appData.icsActive) return;
12827             /* else fall through */
12828           case MachinePlaysWhite:
12829           case MachinePlaysBlack:
12830           case TwoMachinesPlay:
12831             if (forwardMostMove == 0)
12832               return;           /* don't pause if no one has moved */
12833             if ((gameMode == MachinePlaysWhite &&
12834                  !WhiteOnMove(forwardMostMove)) ||
12835                 (gameMode == MachinePlaysBlack &&
12836                  WhiteOnMove(forwardMostMove))) {
12837                 StopClocks();
12838             }
12839             pausing = TRUE;
12840             ModeHighlight();
12841             break;
12842         }
12843     }
12844 }
12845
12846 void
12847 EditCommentEvent()
12848 {
12849     char title[MSG_SIZ];
12850
12851     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12852       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12853     } else {
12854       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12855                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12856                parseList[currentMove - 1]);
12857     }
12858
12859     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12860 }
12861
12862
12863 void
12864 EditTagsEvent()
12865 {
12866     char *tags = PGNTags(&gameInfo);
12867     bookUp = FALSE;
12868     EditTagsPopUp(tags, NULL);
12869     free(tags);
12870 }
12871
12872 void
12873 AnalyzeModeEvent()
12874 {
12875     if (appData.noChessProgram || gameMode == AnalyzeMode)
12876       return;
12877
12878     if (gameMode != AnalyzeFile) {
12879         if (!appData.icsEngineAnalyze) {
12880                EditGameEvent();
12881                if (gameMode != EditGame) return;
12882         }
12883         ResurrectChessProgram();
12884         SendToProgram("analyze\n", &first);
12885         first.analyzing = TRUE;
12886         /*first.maybeThinking = TRUE;*/
12887         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12888         EngineOutputPopUp();
12889     }
12890     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12891     pausing = FALSE;
12892     ModeHighlight();
12893     SetGameInfo();
12894
12895     StartAnalysisClock();
12896     GetTimeMark(&lastNodeCountTime);
12897     lastNodeCount = 0;
12898 }
12899
12900 void
12901 AnalyzeFileEvent()
12902 {
12903     if (appData.noChessProgram || gameMode == AnalyzeFile)
12904       return;
12905
12906     if (gameMode != AnalyzeMode) {
12907         EditGameEvent();
12908         if (gameMode != EditGame) return;
12909         ResurrectChessProgram();
12910         SendToProgram("analyze\n", &first);
12911         first.analyzing = TRUE;
12912         /*first.maybeThinking = TRUE;*/
12913         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12914         EngineOutputPopUp();
12915     }
12916     gameMode = AnalyzeFile;
12917     pausing = FALSE;
12918     ModeHighlight();
12919     SetGameInfo();
12920
12921     StartAnalysisClock();
12922     GetTimeMark(&lastNodeCountTime);
12923     lastNodeCount = 0;
12924     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12925 }
12926
12927 void
12928 MachineWhiteEvent()
12929 {
12930     char buf[MSG_SIZ];
12931     char *bookHit = NULL;
12932
12933     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12934       return;
12935
12936
12937     if (gameMode == PlayFromGameFile ||
12938         gameMode == TwoMachinesPlay  ||
12939         gameMode == Training         ||
12940         gameMode == AnalyzeMode      ||
12941         gameMode == EndOfGame)
12942         EditGameEvent();
12943
12944     if (gameMode == EditPosition)
12945         EditPositionDone(TRUE);
12946
12947     if (!WhiteOnMove(currentMove)) {
12948         DisplayError(_("It is not White's turn"), 0);
12949         return;
12950     }
12951
12952     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12953       ExitAnalyzeMode();
12954
12955     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12956         gameMode == AnalyzeFile)
12957         TruncateGame();
12958
12959     ResurrectChessProgram();    /* in case it isn't running */
12960     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12961         gameMode = MachinePlaysWhite;
12962         ResetClocks();
12963     } else
12964     gameMode = MachinePlaysWhite;
12965     pausing = FALSE;
12966     ModeHighlight();
12967     SetGameInfo();
12968     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12969     DisplayTitle(buf);
12970     if (first.sendName) {
12971       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12972       SendToProgram(buf, &first);
12973     }
12974     if (first.sendTime) {
12975       if (first.useColors) {
12976         SendToProgram("black\n", &first); /*gnu kludge*/
12977       }
12978       SendTimeRemaining(&first, TRUE);
12979     }
12980     if (first.useColors) {
12981       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12982     }
12983     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12984     SetMachineThinkingEnables();
12985     first.maybeThinking = TRUE;
12986     StartClocks();
12987     firstMove = FALSE;
12988
12989     if (appData.autoFlipView && !flipView) {
12990       flipView = !flipView;
12991       DrawPosition(FALSE, NULL);
12992       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12993     }
12994
12995     if(bookHit) { // [HGM] book: simulate book reply
12996         static char bookMove[MSG_SIZ]; // a bit generous?
12997
12998         programStats.nodes = programStats.depth = programStats.time =
12999         programStats.score = programStats.got_only_move = 0;
13000         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13001
13002         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13003         strcat(bookMove, bookHit);
13004         HandleMachineMove(bookMove, &first);
13005     }
13006 }
13007
13008 void
13009 MachineBlackEvent()
13010 {
13011   char buf[MSG_SIZ];
13012   char *bookHit = NULL;
13013
13014     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13015         return;
13016
13017
13018     if (gameMode == PlayFromGameFile ||
13019         gameMode == TwoMachinesPlay  ||
13020         gameMode == Training         ||
13021         gameMode == AnalyzeMode      ||
13022         gameMode == EndOfGame)
13023         EditGameEvent();
13024
13025     if (gameMode == EditPosition)
13026         EditPositionDone(TRUE);
13027
13028     if (WhiteOnMove(currentMove)) {
13029         DisplayError(_("It is not Black's turn"), 0);
13030         return;
13031     }
13032
13033     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13034       ExitAnalyzeMode();
13035
13036     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13037         gameMode == AnalyzeFile)
13038         TruncateGame();
13039
13040     ResurrectChessProgram();    /* in case it isn't running */
13041     gameMode = MachinePlaysBlack;
13042     pausing = FALSE;
13043     ModeHighlight();
13044     SetGameInfo();
13045     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13046     DisplayTitle(buf);
13047     if (first.sendName) {
13048       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13049       SendToProgram(buf, &first);
13050     }
13051     if (first.sendTime) {
13052       if (first.useColors) {
13053         SendToProgram("white\n", &first); /*gnu kludge*/
13054       }
13055       SendTimeRemaining(&first, FALSE);
13056     }
13057     if (first.useColors) {
13058       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13059     }
13060     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13061     SetMachineThinkingEnables();
13062     first.maybeThinking = TRUE;
13063     StartClocks();
13064
13065     if (appData.autoFlipView && flipView) {
13066       flipView = !flipView;
13067       DrawPosition(FALSE, NULL);
13068       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13069     }
13070     if(bookHit) { // [HGM] book: simulate book reply
13071         static char bookMove[MSG_SIZ]; // a bit generous?
13072
13073         programStats.nodes = programStats.depth = programStats.time =
13074         programStats.score = programStats.got_only_move = 0;
13075         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13076
13077         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13078         strcat(bookMove, bookHit);
13079         HandleMachineMove(bookMove, &first);
13080     }
13081 }
13082
13083
13084 void
13085 DisplayTwoMachinesTitle()
13086 {
13087     char buf[MSG_SIZ];
13088     if (appData.matchGames > 0) {
13089         if(appData.tourneyFile[0]) {
13090           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13091                    gameInfo.white, gameInfo.black,
13092                    nextGame+1, appData.matchGames+1,
13093                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13094         } else 
13095         if (first.twoMachinesColor[0] == 'w') {
13096           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13097                    gameInfo.white, gameInfo.black,
13098                    first.matchWins, second.matchWins,
13099                    matchGame - 1 - (first.matchWins + second.matchWins));
13100         } else {
13101           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13102                    gameInfo.white, gameInfo.black,
13103                    second.matchWins, first.matchWins,
13104                    matchGame - 1 - (first.matchWins + second.matchWins));
13105         }
13106     } else {
13107       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13108     }
13109     DisplayTitle(buf);
13110 }
13111
13112 void
13113 SettingsMenuIfReady()
13114 {
13115   if (second.lastPing != second.lastPong) {
13116     DisplayMessage("", _("Waiting for second chess program"));
13117     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13118     return;
13119   }
13120   ThawUI();
13121   DisplayMessage("", "");
13122   SettingsPopUp(&second);
13123 }
13124
13125 int
13126 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13127 {
13128     char buf[MSG_SIZ];
13129     if (cps->pr == NULL) {
13130         StartChessProgram(cps);
13131         if (cps->protocolVersion == 1) {
13132           retry();
13133         } else {
13134           /* kludge: allow timeout for initial "feature" command */
13135           FreezeUI();
13136           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13137           DisplayMessage("", buf);
13138           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13139         }
13140         return 1;
13141     }
13142     return 0;
13143 }
13144
13145 void
13146 TwoMachinesEvent P((void))
13147 {
13148     int i;
13149     char buf[MSG_SIZ];
13150     ChessProgramState *onmove;
13151     char *bookHit = NULL;
13152     static int stalling = 0;
13153     TimeMark now;
13154     long wait;
13155
13156     if (appData.noChessProgram) return;
13157
13158     switch (gameMode) {
13159       case TwoMachinesPlay:
13160         return;
13161       case MachinePlaysWhite:
13162       case MachinePlaysBlack:
13163         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13164             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13165             return;
13166         }
13167         /* fall through */
13168       case BeginningOfGame:
13169       case PlayFromGameFile:
13170       case EndOfGame:
13171         EditGameEvent();
13172         if (gameMode != EditGame) return;
13173         break;
13174       case EditPosition:
13175         EditPositionDone(TRUE);
13176         break;
13177       case AnalyzeMode:
13178       case AnalyzeFile:
13179         ExitAnalyzeMode();
13180         break;
13181       case EditGame:
13182       default:
13183         break;
13184     }
13185
13186 //    forwardMostMove = currentMove;
13187     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13188
13189     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13190
13191     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13192     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13193       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13194       return;
13195     }
13196     if(!stalling) {
13197       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13198       SendToProgram("force\n", &second);
13199       stalling = 1;
13200       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13201       return;
13202     }
13203     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13204     if(appData.matchPause>10000 || appData.matchPause<10)
13205                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13206     wait = SubtractTimeMarks(&now, &pauseStart);
13207     if(wait < appData.matchPause) {
13208         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13209         return;
13210     }
13211     stalling = 0;
13212     DisplayMessage("", "");
13213     if (startedFromSetupPosition) {
13214         SendBoard(&second, backwardMostMove);
13215     if (appData.debugMode) {
13216         fprintf(debugFP, "Two Machines\n");
13217     }
13218     }
13219     for (i = backwardMostMove; i < forwardMostMove; i++) {
13220         SendMoveToProgram(i, &second);
13221     }
13222
13223     gameMode = TwoMachinesPlay;
13224     pausing = FALSE;
13225     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13226     SetGameInfo();
13227     DisplayTwoMachinesTitle();
13228     firstMove = TRUE;
13229     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13230         onmove = &first;
13231     } else {
13232         onmove = &second;
13233     }
13234     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13235     SendToProgram(first.computerString, &first);
13236     if (first.sendName) {
13237       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13238       SendToProgram(buf, &first);
13239     }
13240     SendToProgram(second.computerString, &second);
13241     if (second.sendName) {
13242       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13243       SendToProgram(buf, &second);
13244     }
13245
13246     ResetClocks();
13247     if (!first.sendTime || !second.sendTime) {
13248         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13249         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13250     }
13251     if (onmove->sendTime) {
13252       if (onmove->useColors) {
13253         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13254       }
13255       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13256     }
13257     if (onmove->useColors) {
13258       SendToProgram(onmove->twoMachinesColor, onmove);
13259     }
13260     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13261 //    SendToProgram("go\n", onmove);
13262     onmove->maybeThinking = TRUE;
13263     SetMachineThinkingEnables();
13264
13265     StartClocks();
13266
13267     if(bookHit) { // [HGM] book: simulate book reply
13268         static char bookMove[MSG_SIZ]; // a bit generous?
13269
13270         programStats.nodes = programStats.depth = programStats.time =
13271         programStats.score = programStats.got_only_move = 0;
13272         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13273
13274         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13275         strcat(bookMove, bookHit);
13276         savedMessage = bookMove; // args for deferred call
13277         savedState = onmove;
13278         ScheduleDelayedEvent(DeferredBookMove, 1);
13279     }
13280 }
13281
13282 void
13283 TrainingEvent()
13284 {
13285     if (gameMode == Training) {
13286       SetTrainingModeOff();
13287       gameMode = PlayFromGameFile;
13288       DisplayMessage("", _("Training mode off"));
13289     } else {
13290       gameMode = Training;
13291       animateTraining = appData.animate;
13292
13293       /* make sure we are not already at the end of the game */
13294       if (currentMove < forwardMostMove) {
13295         SetTrainingModeOn();
13296         DisplayMessage("", _("Training mode on"));
13297       } else {
13298         gameMode = PlayFromGameFile;
13299         DisplayError(_("Already at end of game"), 0);
13300       }
13301     }
13302     ModeHighlight();
13303 }
13304
13305 void
13306 IcsClientEvent()
13307 {
13308     if (!appData.icsActive) return;
13309     switch (gameMode) {
13310       case IcsPlayingWhite:
13311       case IcsPlayingBlack:
13312       case IcsObserving:
13313       case IcsIdle:
13314       case BeginningOfGame:
13315       case IcsExamining:
13316         return;
13317
13318       case EditGame:
13319         break;
13320
13321       case EditPosition:
13322         EditPositionDone(TRUE);
13323         break;
13324
13325       case AnalyzeMode:
13326       case AnalyzeFile:
13327         ExitAnalyzeMode();
13328         break;
13329
13330       default:
13331         EditGameEvent();
13332         break;
13333     }
13334
13335     gameMode = IcsIdle;
13336     ModeHighlight();
13337     return;
13338 }
13339
13340
13341 void
13342 EditGameEvent()
13343 {
13344     int i;
13345
13346     switch (gameMode) {
13347       case Training:
13348         SetTrainingModeOff();
13349         break;
13350       case MachinePlaysWhite:
13351       case MachinePlaysBlack:
13352       case BeginningOfGame:
13353         SendToProgram("force\n", &first);
13354         SetUserThinkingEnables();
13355         break;
13356       case PlayFromGameFile:
13357         (void) StopLoadGameTimer();
13358         if (gameFileFP != NULL) {
13359             gameFileFP = NULL;
13360         }
13361         break;
13362       case EditPosition:
13363         EditPositionDone(TRUE);
13364         break;
13365       case AnalyzeMode:
13366       case AnalyzeFile:
13367         ExitAnalyzeMode();
13368         SendToProgram("force\n", &first);
13369         break;
13370       case TwoMachinesPlay:
13371         GameEnds(EndOfFile, NULL, GE_PLAYER);
13372         ResurrectChessProgram();
13373         SetUserThinkingEnables();
13374         break;
13375       case EndOfGame:
13376         ResurrectChessProgram();
13377         break;
13378       case IcsPlayingBlack:
13379       case IcsPlayingWhite:
13380         DisplayError(_("Warning: You are still playing a game"), 0);
13381         break;
13382       case IcsObserving:
13383         DisplayError(_("Warning: You are still observing a game"), 0);
13384         break;
13385       case IcsExamining:
13386         DisplayError(_("Warning: You are still examining a game"), 0);
13387         break;
13388       case IcsIdle:
13389         break;
13390       case EditGame:
13391       default:
13392         return;
13393     }
13394
13395     pausing = FALSE;
13396     StopClocks();
13397     first.offeredDraw = second.offeredDraw = 0;
13398
13399     if (gameMode == PlayFromGameFile) {
13400         whiteTimeRemaining = timeRemaining[0][currentMove];
13401         blackTimeRemaining = timeRemaining[1][currentMove];
13402         DisplayTitle("");
13403     }
13404
13405     if (gameMode == MachinePlaysWhite ||
13406         gameMode == MachinePlaysBlack ||
13407         gameMode == TwoMachinesPlay ||
13408         gameMode == EndOfGame) {
13409         i = forwardMostMove;
13410         while (i > currentMove) {
13411             SendToProgram("undo\n", &first);
13412             i--;
13413         }
13414         whiteTimeRemaining = timeRemaining[0][currentMove];
13415         blackTimeRemaining = timeRemaining[1][currentMove];
13416         DisplayBothClocks();
13417         if (whiteFlag || blackFlag) {
13418             whiteFlag = blackFlag = 0;
13419         }
13420         DisplayTitle("");
13421     }
13422
13423     gameMode = EditGame;
13424     ModeHighlight();
13425     SetGameInfo();
13426 }
13427
13428
13429 void
13430 EditPositionEvent()
13431 {
13432     if (gameMode == EditPosition) {
13433         EditGameEvent();
13434         return;
13435     }
13436
13437     EditGameEvent();
13438     if (gameMode != EditGame) return;
13439
13440     gameMode = EditPosition;
13441     ModeHighlight();
13442     SetGameInfo();
13443     if (currentMove > 0)
13444       CopyBoard(boards[0], boards[currentMove]);
13445
13446     blackPlaysFirst = !WhiteOnMove(currentMove);
13447     ResetClocks();
13448     currentMove = forwardMostMove = backwardMostMove = 0;
13449     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13450     DisplayMove(-1);
13451 }
13452
13453 void
13454 ExitAnalyzeMode()
13455 {
13456     /* [DM] icsEngineAnalyze - possible call from other functions */
13457     if (appData.icsEngineAnalyze) {
13458         appData.icsEngineAnalyze = FALSE;
13459
13460         DisplayMessage("",_("Close ICS engine analyze..."));
13461     }
13462     if (first.analysisSupport && first.analyzing) {
13463       SendToProgram("exit\n", &first);
13464       first.analyzing = FALSE;
13465     }
13466     thinkOutput[0] = NULLCHAR;
13467 }
13468
13469 void
13470 EditPositionDone(Boolean fakeRights)
13471 {
13472     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13473
13474     startedFromSetupPosition = TRUE;
13475     InitChessProgram(&first, FALSE);
13476     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13477       boards[0][EP_STATUS] = EP_NONE;
13478       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13479     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13480         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13481         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13482       } else boards[0][CASTLING][2] = NoRights;
13483     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13484         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13485         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13486       } else boards[0][CASTLING][5] = NoRights;
13487     }
13488     SendToProgram("force\n", &first);
13489     if (blackPlaysFirst) {
13490         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13491         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13492         currentMove = forwardMostMove = backwardMostMove = 1;
13493         CopyBoard(boards[1], boards[0]);
13494     } else {
13495         currentMove = forwardMostMove = backwardMostMove = 0;
13496     }
13497     SendBoard(&first, forwardMostMove);
13498     if (appData.debugMode) {
13499         fprintf(debugFP, "EditPosDone\n");
13500     }
13501     DisplayTitle("");
13502     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13503     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13504     gameMode = EditGame;
13505     ModeHighlight();
13506     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13507     ClearHighlights(); /* [AS] */
13508 }
13509
13510 /* Pause for `ms' milliseconds */
13511 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13512 void
13513 TimeDelay(ms)
13514      long ms;
13515 {
13516     TimeMark m1, m2;
13517
13518     GetTimeMark(&m1);
13519     do {
13520         GetTimeMark(&m2);
13521     } while (SubtractTimeMarks(&m2, &m1) < ms);
13522 }
13523
13524 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13525 void
13526 SendMultiLineToICS(buf)
13527      char *buf;
13528 {
13529     char temp[MSG_SIZ+1], *p;
13530     int len;
13531
13532     len = strlen(buf);
13533     if (len > MSG_SIZ)
13534       len = MSG_SIZ;
13535
13536     strncpy(temp, buf, len);
13537     temp[len] = 0;
13538
13539     p = temp;
13540     while (*p) {
13541         if (*p == '\n' || *p == '\r')
13542           *p = ' ';
13543         ++p;
13544     }
13545
13546     strcat(temp, "\n");
13547     SendToICS(temp);
13548     SendToPlayer(temp, strlen(temp));
13549 }
13550
13551 void
13552 SetWhiteToPlayEvent()
13553 {
13554     if (gameMode == EditPosition) {
13555         blackPlaysFirst = FALSE;
13556         DisplayBothClocks();    /* works because currentMove is 0 */
13557     } else if (gameMode == IcsExamining) {
13558         SendToICS(ics_prefix);
13559         SendToICS("tomove white\n");
13560     }
13561 }
13562
13563 void
13564 SetBlackToPlayEvent()
13565 {
13566     if (gameMode == EditPosition) {
13567         blackPlaysFirst = TRUE;
13568         currentMove = 1;        /* kludge */
13569         DisplayBothClocks();
13570         currentMove = 0;
13571     } else if (gameMode == IcsExamining) {
13572         SendToICS(ics_prefix);
13573         SendToICS("tomove black\n");
13574     }
13575 }
13576
13577 void
13578 EditPositionMenuEvent(selection, x, y)
13579      ChessSquare selection;
13580      int x, y;
13581 {
13582     char buf[MSG_SIZ];
13583     ChessSquare piece = boards[0][y][x];
13584
13585     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13586
13587     switch (selection) {
13588       case ClearBoard:
13589         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13590             SendToICS(ics_prefix);
13591             SendToICS("bsetup clear\n");
13592         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13593             SendToICS(ics_prefix);
13594             SendToICS("clearboard\n");
13595         } else {
13596             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13597                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13598                 for (y = 0; y < BOARD_HEIGHT; y++) {
13599                     if (gameMode == IcsExamining) {
13600                         if (boards[currentMove][y][x] != EmptySquare) {
13601                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13602                                     AAA + x, ONE + y);
13603                             SendToICS(buf);
13604                         }
13605                     } else {
13606                         boards[0][y][x] = p;
13607                     }
13608                 }
13609             }
13610         }
13611         if (gameMode == EditPosition) {
13612             DrawPosition(FALSE, boards[0]);
13613         }
13614         break;
13615
13616       case WhitePlay:
13617         SetWhiteToPlayEvent();
13618         break;
13619
13620       case BlackPlay:
13621         SetBlackToPlayEvent();
13622         break;
13623
13624       case EmptySquare:
13625         if (gameMode == IcsExamining) {
13626             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13627             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13628             SendToICS(buf);
13629         } else {
13630             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13631                 if(x == BOARD_LEFT-2) {
13632                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13633                     boards[0][y][1] = 0;
13634                 } else
13635                 if(x == BOARD_RGHT+1) {
13636                     if(y >= gameInfo.holdingsSize) break;
13637                     boards[0][y][BOARD_WIDTH-2] = 0;
13638                 } else break;
13639             }
13640             boards[0][y][x] = EmptySquare;
13641             DrawPosition(FALSE, boards[0]);
13642         }
13643         break;
13644
13645       case PromotePiece:
13646         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13647            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13648             selection = (ChessSquare) (PROMOTED piece);
13649         } else if(piece == EmptySquare) selection = WhiteSilver;
13650         else selection = (ChessSquare)((int)piece - 1);
13651         goto defaultlabel;
13652
13653       case DemotePiece:
13654         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13655            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13656             selection = (ChessSquare) (DEMOTED piece);
13657         } else if(piece == EmptySquare) selection = BlackSilver;
13658         else selection = (ChessSquare)((int)piece + 1);
13659         goto defaultlabel;
13660
13661       case WhiteQueen:
13662       case BlackQueen:
13663         if(gameInfo.variant == VariantShatranj ||
13664            gameInfo.variant == VariantXiangqi  ||
13665            gameInfo.variant == VariantCourier  ||
13666            gameInfo.variant == VariantMakruk     )
13667             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13668         goto defaultlabel;
13669
13670       case WhiteKing:
13671       case BlackKing:
13672         if(gameInfo.variant == VariantXiangqi)
13673             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13674         if(gameInfo.variant == VariantKnightmate)
13675             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13676       default:
13677         defaultlabel:
13678         if (gameMode == IcsExamining) {
13679             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13680             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13681                      PieceToChar(selection), AAA + x, ONE + y);
13682             SendToICS(buf);
13683         } else {
13684             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13685                 int n;
13686                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13687                     n = PieceToNumber(selection - BlackPawn);
13688                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13689                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13690                     boards[0][BOARD_HEIGHT-1-n][1]++;
13691                 } else
13692                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13693                     n = PieceToNumber(selection);
13694                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13695                     boards[0][n][BOARD_WIDTH-1] = selection;
13696                     boards[0][n][BOARD_WIDTH-2]++;
13697                 }
13698             } else
13699             boards[0][y][x] = selection;
13700             DrawPosition(TRUE, boards[0]);
13701         }
13702         break;
13703     }
13704 }
13705
13706
13707 void
13708 DropMenuEvent(selection, x, y)
13709      ChessSquare selection;
13710      int x, y;
13711 {
13712     ChessMove moveType;
13713
13714     switch (gameMode) {
13715       case IcsPlayingWhite:
13716       case MachinePlaysBlack:
13717         if (!WhiteOnMove(currentMove)) {
13718             DisplayMoveError(_("It is Black's turn"));
13719             return;
13720         }
13721         moveType = WhiteDrop;
13722         break;
13723       case IcsPlayingBlack:
13724       case MachinePlaysWhite:
13725         if (WhiteOnMove(currentMove)) {
13726             DisplayMoveError(_("It is White's turn"));
13727             return;
13728         }
13729         moveType = BlackDrop;
13730         break;
13731       case EditGame:
13732         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13733         break;
13734       default:
13735         return;
13736     }
13737
13738     if (moveType == BlackDrop && selection < BlackPawn) {
13739       selection = (ChessSquare) ((int) selection
13740                                  + (int) BlackPawn - (int) WhitePawn);
13741     }
13742     if (boards[currentMove][y][x] != EmptySquare) {
13743         DisplayMoveError(_("That square is occupied"));
13744         return;
13745     }
13746
13747     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13748 }
13749
13750 void
13751 AcceptEvent()
13752 {
13753     /* Accept a pending offer of any kind from opponent */
13754
13755     if (appData.icsActive) {
13756         SendToICS(ics_prefix);
13757         SendToICS("accept\n");
13758     } else if (cmailMsgLoaded) {
13759         if (currentMove == cmailOldMove &&
13760             commentList[cmailOldMove] != NULL &&
13761             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13762                    "Black offers a draw" : "White offers a draw")) {
13763             TruncateGame();
13764             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13765             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13766         } else {
13767             DisplayError(_("There is no pending offer on this move"), 0);
13768             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13769         }
13770     } else {
13771         /* Not used for offers from chess program */
13772     }
13773 }
13774
13775 void
13776 DeclineEvent()
13777 {
13778     /* Decline a pending offer of any kind from opponent */
13779
13780     if (appData.icsActive) {
13781         SendToICS(ics_prefix);
13782         SendToICS("decline\n");
13783     } else if (cmailMsgLoaded) {
13784         if (currentMove == cmailOldMove &&
13785             commentList[cmailOldMove] != NULL &&
13786             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13787                    "Black offers a draw" : "White offers a draw")) {
13788 #ifdef NOTDEF
13789             AppendComment(cmailOldMove, "Draw declined", TRUE);
13790             DisplayComment(cmailOldMove - 1, "Draw declined");
13791 #endif /*NOTDEF*/
13792         } else {
13793             DisplayError(_("There is no pending offer on this move"), 0);
13794         }
13795     } else {
13796         /* Not used for offers from chess program */
13797     }
13798 }
13799
13800 void
13801 RematchEvent()
13802 {
13803     /* Issue ICS rematch command */
13804     if (appData.icsActive) {
13805         SendToICS(ics_prefix);
13806         SendToICS("rematch\n");
13807     }
13808 }
13809
13810 void
13811 CallFlagEvent()
13812 {
13813     /* Call your opponent's flag (claim a win on time) */
13814     if (appData.icsActive) {
13815         SendToICS(ics_prefix);
13816         SendToICS("flag\n");
13817     } else {
13818         switch (gameMode) {
13819           default:
13820             return;
13821           case MachinePlaysWhite:
13822             if (whiteFlag) {
13823                 if (blackFlag)
13824                   GameEnds(GameIsDrawn, "Both players ran out of time",
13825                            GE_PLAYER);
13826                 else
13827                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13828             } else {
13829                 DisplayError(_("Your opponent is not out of time"), 0);
13830             }
13831             break;
13832           case MachinePlaysBlack:
13833             if (blackFlag) {
13834                 if (whiteFlag)
13835                   GameEnds(GameIsDrawn, "Both players ran out of time",
13836                            GE_PLAYER);
13837                 else
13838                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13839             } else {
13840                 DisplayError(_("Your opponent is not out of time"), 0);
13841             }
13842             break;
13843         }
13844     }
13845 }
13846
13847 void
13848 ClockClick(int which)
13849 {       // [HGM] code moved to back-end from winboard.c
13850         if(which) { // black clock
13851           if (gameMode == EditPosition || gameMode == IcsExamining) {
13852             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13853             SetBlackToPlayEvent();
13854           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13855           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13856           } else if (shiftKey) {
13857             AdjustClock(which, -1);
13858           } else if (gameMode == IcsPlayingWhite ||
13859                      gameMode == MachinePlaysBlack) {
13860             CallFlagEvent();
13861           }
13862         } else { // white clock
13863           if (gameMode == EditPosition || gameMode == IcsExamining) {
13864             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13865             SetWhiteToPlayEvent();
13866           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13867           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13868           } else if (shiftKey) {
13869             AdjustClock(which, -1);
13870           } else if (gameMode == IcsPlayingBlack ||
13871                    gameMode == MachinePlaysWhite) {
13872             CallFlagEvent();
13873           }
13874         }
13875 }
13876
13877 void
13878 DrawEvent()
13879 {
13880     /* Offer draw or accept pending draw offer from opponent */
13881
13882     if (appData.icsActive) {
13883         /* Note: tournament rules require draw offers to be
13884            made after you make your move but before you punch
13885            your clock.  Currently ICS doesn't let you do that;
13886            instead, you immediately punch your clock after making
13887            a move, but you can offer a draw at any time. */
13888
13889         SendToICS(ics_prefix);
13890         SendToICS("draw\n");
13891         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13892     } else if (cmailMsgLoaded) {
13893         if (currentMove == cmailOldMove &&
13894             commentList[cmailOldMove] != NULL &&
13895             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13896                    "Black offers a draw" : "White offers a draw")) {
13897             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13898             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13899         } else if (currentMove == cmailOldMove + 1) {
13900             char *offer = WhiteOnMove(cmailOldMove) ?
13901               "White offers a draw" : "Black offers a draw";
13902             AppendComment(currentMove, offer, TRUE);
13903             DisplayComment(currentMove - 1, offer);
13904             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13905         } else {
13906             DisplayError(_("You must make your move before offering a draw"), 0);
13907             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13908         }
13909     } else if (first.offeredDraw) {
13910         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13911     } else {
13912         if (first.sendDrawOffers) {
13913             SendToProgram("draw\n", &first);
13914             userOfferedDraw = TRUE;
13915         }
13916     }
13917 }
13918
13919 void
13920 AdjournEvent()
13921 {
13922     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13923
13924     if (appData.icsActive) {
13925         SendToICS(ics_prefix);
13926         SendToICS("adjourn\n");
13927     } else {
13928         /* Currently GNU Chess doesn't offer or accept Adjourns */
13929     }
13930 }
13931
13932
13933 void
13934 AbortEvent()
13935 {
13936     /* Offer Abort or accept pending Abort offer from opponent */
13937
13938     if (appData.icsActive) {
13939         SendToICS(ics_prefix);
13940         SendToICS("abort\n");
13941     } else {
13942         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13943     }
13944 }
13945
13946 void
13947 ResignEvent()
13948 {
13949     /* Resign.  You can do this even if it's not your turn. */
13950
13951     if (appData.icsActive) {
13952         SendToICS(ics_prefix);
13953         SendToICS("resign\n");
13954     } else {
13955         switch (gameMode) {
13956           case MachinePlaysWhite:
13957             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13958             break;
13959           case MachinePlaysBlack:
13960             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13961             break;
13962           case EditGame:
13963             if (cmailMsgLoaded) {
13964                 TruncateGame();
13965                 if (WhiteOnMove(cmailOldMove)) {
13966                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13967                 } else {
13968                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13969                 }
13970                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13971             }
13972             break;
13973           default:
13974             break;
13975         }
13976     }
13977 }
13978
13979
13980 void
13981 StopObservingEvent()
13982 {
13983     /* Stop observing current games */
13984     SendToICS(ics_prefix);
13985     SendToICS("unobserve\n");
13986 }
13987
13988 void
13989 StopExaminingEvent()
13990 {
13991     /* Stop observing current game */
13992     SendToICS(ics_prefix);
13993     SendToICS("unexamine\n");
13994 }
13995
13996 void
13997 ForwardInner(target)
13998      int target;
13999 {
14000     int limit;
14001
14002     if (appData.debugMode)
14003         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14004                 target, currentMove, forwardMostMove);
14005
14006     if (gameMode == EditPosition)
14007       return;
14008
14009     if (gameMode == PlayFromGameFile && !pausing)
14010       PauseEvent();
14011
14012     if (gameMode == IcsExamining && pausing)
14013       limit = pauseExamForwardMostMove;
14014     else
14015       limit = forwardMostMove;
14016
14017     if (target > limit) target = limit;
14018
14019     if (target > 0 && moveList[target - 1][0]) {
14020         int fromX, fromY, toX, toY;
14021         toX = moveList[target - 1][2] - AAA;
14022         toY = moveList[target - 1][3] - ONE;
14023         if (moveList[target - 1][1] == '@') {
14024             if (appData.highlightLastMove) {
14025                 SetHighlights(-1, -1, toX, toY);
14026             }
14027         } else {
14028             fromX = moveList[target - 1][0] - AAA;
14029             fromY = moveList[target - 1][1] - ONE;
14030             if (target == currentMove + 1) {
14031                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14032             }
14033             if (appData.highlightLastMove) {
14034                 SetHighlights(fromX, fromY, toX, toY);
14035             }
14036         }
14037     }
14038     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14039         gameMode == Training || gameMode == PlayFromGameFile ||
14040         gameMode == AnalyzeFile) {
14041         while (currentMove < target) {
14042             SendMoveToProgram(currentMove++, &first);
14043         }
14044     } else {
14045         currentMove = target;
14046     }
14047
14048     if (gameMode == EditGame || gameMode == EndOfGame) {
14049         whiteTimeRemaining = timeRemaining[0][currentMove];
14050         blackTimeRemaining = timeRemaining[1][currentMove];
14051     }
14052     DisplayBothClocks();
14053     DisplayMove(currentMove - 1);
14054     DrawPosition(FALSE, boards[currentMove]);
14055     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14056     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14057         DisplayComment(currentMove - 1, commentList[currentMove]);
14058     }
14059     DisplayBook(currentMove);
14060 }
14061
14062
14063 void
14064 ForwardEvent()
14065 {
14066     if (gameMode == IcsExamining && !pausing) {
14067         SendToICS(ics_prefix);
14068         SendToICS("forward\n");
14069     } else {
14070         ForwardInner(currentMove + 1);
14071     }
14072 }
14073
14074 void
14075 ToEndEvent()
14076 {
14077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14078         /* to optimze, we temporarily turn off analysis mode while we feed
14079          * the remaining moves to the engine. Otherwise we get analysis output
14080          * after each move.
14081          */
14082         if (first.analysisSupport) {
14083           SendToProgram("exit\nforce\n", &first);
14084           first.analyzing = FALSE;
14085         }
14086     }
14087
14088     if (gameMode == IcsExamining && !pausing) {
14089         SendToICS(ics_prefix);
14090         SendToICS("forward 999999\n");
14091     } else {
14092         ForwardInner(forwardMostMove);
14093     }
14094
14095     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14096         /* we have fed all the moves, so reactivate analysis mode */
14097         SendToProgram("analyze\n", &first);
14098         first.analyzing = TRUE;
14099         /*first.maybeThinking = TRUE;*/
14100         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14101     }
14102 }
14103
14104 void
14105 BackwardInner(target)
14106      int target;
14107 {
14108     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14109
14110     if (appData.debugMode)
14111         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14112                 target, currentMove, forwardMostMove);
14113
14114     if (gameMode == EditPosition) return;
14115     if (currentMove <= backwardMostMove) {
14116         ClearHighlights();
14117         DrawPosition(full_redraw, boards[currentMove]);
14118         return;
14119     }
14120     if (gameMode == PlayFromGameFile && !pausing)
14121       PauseEvent();
14122
14123     if (moveList[target][0]) {
14124         int fromX, fromY, toX, toY;
14125         toX = moveList[target][2] - AAA;
14126         toY = moveList[target][3] - ONE;
14127         if (moveList[target][1] == '@') {
14128             if (appData.highlightLastMove) {
14129                 SetHighlights(-1, -1, toX, toY);
14130             }
14131         } else {
14132             fromX = moveList[target][0] - AAA;
14133             fromY = moveList[target][1] - ONE;
14134             if (target == currentMove - 1) {
14135                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14136             }
14137             if (appData.highlightLastMove) {
14138                 SetHighlights(fromX, fromY, toX, toY);
14139             }
14140         }
14141     }
14142     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14143         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14144         while (currentMove > target) {
14145             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14146                 // null move cannot be undone. Reload program with move history before it.
14147                 int i;
14148                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14149                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14150                 }
14151                 SendBoard(&first, i); 
14152                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14153                 break;
14154             }
14155             SendToProgram("undo\n", &first);
14156             currentMove--;
14157         }
14158     } else {
14159         currentMove = target;
14160     }
14161
14162     if (gameMode == EditGame || gameMode == EndOfGame) {
14163         whiteTimeRemaining = timeRemaining[0][currentMove];
14164         blackTimeRemaining = timeRemaining[1][currentMove];
14165     }
14166     DisplayBothClocks();
14167     DisplayMove(currentMove - 1);
14168     DrawPosition(full_redraw, boards[currentMove]);
14169     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14170     // [HGM] PV info: routine tests if comment empty
14171     DisplayComment(currentMove - 1, commentList[currentMove]);
14172     DisplayBook(currentMove);
14173 }
14174
14175 void
14176 BackwardEvent()
14177 {
14178     if (gameMode == IcsExamining && !pausing) {
14179         SendToICS(ics_prefix);
14180         SendToICS("backward\n");
14181     } else {
14182         BackwardInner(currentMove - 1);
14183     }
14184 }
14185
14186 void
14187 ToStartEvent()
14188 {
14189     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14190         /* to optimize, we temporarily turn off analysis mode while we undo
14191          * all the moves. Otherwise we get analysis output after each undo.
14192          */
14193         if (first.analysisSupport) {
14194           SendToProgram("exit\nforce\n", &first);
14195           first.analyzing = FALSE;
14196         }
14197     }
14198
14199     if (gameMode == IcsExamining && !pausing) {
14200         SendToICS(ics_prefix);
14201         SendToICS("backward 999999\n");
14202     } else {
14203         BackwardInner(backwardMostMove);
14204     }
14205
14206     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14207         /* we have fed all the moves, so reactivate analysis mode */
14208         SendToProgram("analyze\n", &first);
14209         first.analyzing = TRUE;
14210         /*first.maybeThinking = TRUE;*/
14211         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14212     }
14213 }
14214
14215 void
14216 ToNrEvent(int to)
14217 {
14218   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14219   if (to >= forwardMostMove) to = forwardMostMove;
14220   if (to <= backwardMostMove) to = backwardMostMove;
14221   if (to < currentMove) {
14222     BackwardInner(to);
14223   } else {
14224     ForwardInner(to);
14225   }
14226 }
14227
14228 void
14229 RevertEvent(Boolean annotate)
14230 {
14231     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14232         return;
14233     }
14234     if (gameMode != IcsExamining) {
14235         DisplayError(_("You are not examining a game"), 0);
14236         return;
14237     }
14238     if (pausing) {
14239         DisplayError(_("You can't revert while pausing"), 0);
14240         return;
14241     }
14242     SendToICS(ics_prefix);
14243     SendToICS("revert\n");
14244 }
14245
14246 void
14247 RetractMoveEvent()
14248 {
14249     switch (gameMode) {
14250       case MachinePlaysWhite:
14251       case MachinePlaysBlack:
14252         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14253             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14254             return;
14255         }
14256         if (forwardMostMove < 2) return;
14257         currentMove = forwardMostMove = forwardMostMove - 2;
14258         whiteTimeRemaining = timeRemaining[0][currentMove];
14259         blackTimeRemaining = timeRemaining[1][currentMove];
14260         DisplayBothClocks();
14261         DisplayMove(currentMove - 1);
14262         ClearHighlights();/*!! could figure this out*/
14263         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14264         SendToProgram("remove\n", &first);
14265         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14266         break;
14267
14268       case BeginningOfGame:
14269       default:
14270         break;
14271
14272       case IcsPlayingWhite:
14273       case IcsPlayingBlack:
14274         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14275             SendToICS(ics_prefix);
14276             SendToICS("takeback 2\n");
14277         } else {
14278             SendToICS(ics_prefix);
14279             SendToICS("takeback 1\n");
14280         }
14281         break;
14282     }
14283 }
14284
14285 void
14286 MoveNowEvent()
14287 {
14288     ChessProgramState *cps;
14289
14290     switch (gameMode) {
14291       case MachinePlaysWhite:
14292         if (!WhiteOnMove(forwardMostMove)) {
14293             DisplayError(_("It is your turn"), 0);
14294             return;
14295         }
14296         cps = &first;
14297         break;
14298       case MachinePlaysBlack:
14299         if (WhiteOnMove(forwardMostMove)) {
14300             DisplayError(_("It is your turn"), 0);
14301             return;
14302         }
14303         cps = &first;
14304         break;
14305       case TwoMachinesPlay:
14306         if (WhiteOnMove(forwardMostMove) ==
14307             (first.twoMachinesColor[0] == 'w')) {
14308             cps = &first;
14309         } else {
14310             cps = &second;
14311         }
14312         break;
14313       case BeginningOfGame:
14314       default:
14315         return;
14316     }
14317     SendToProgram("?\n", cps);
14318 }
14319
14320 void
14321 TruncateGameEvent()
14322 {
14323     EditGameEvent();
14324     if (gameMode != EditGame) return;
14325     TruncateGame();
14326 }
14327
14328 void
14329 TruncateGame()
14330 {
14331     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14332     if (forwardMostMove > currentMove) {
14333         if (gameInfo.resultDetails != NULL) {
14334             free(gameInfo.resultDetails);
14335             gameInfo.resultDetails = NULL;
14336             gameInfo.result = GameUnfinished;
14337         }
14338         forwardMostMove = currentMove;
14339         HistorySet(parseList, backwardMostMove, forwardMostMove,
14340                    currentMove-1);
14341     }
14342 }
14343
14344 void
14345 HintEvent()
14346 {
14347     if (appData.noChessProgram) return;
14348     switch (gameMode) {
14349       case MachinePlaysWhite:
14350         if (WhiteOnMove(forwardMostMove)) {
14351             DisplayError(_("Wait until your turn"), 0);
14352             return;
14353         }
14354         break;
14355       case BeginningOfGame:
14356       case MachinePlaysBlack:
14357         if (!WhiteOnMove(forwardMostMove)) {
14358             DisplayError(_("Wait until your turn"), 0);
14359             return;
14360         }
14361         break;
14362       default:
14363         DisplayError(_("No hint available"), 0);
14364         return;
14365     }
14366     SendToProgram("hint\n", &first);
14367     hintRequested = TRUE;
14368 }
14369
14370 void
14371 BookEvent()
14372 {
14373     if (appData.noChessProgram) return;
14374     switch (gameMode) {
14375       case MachinePlaysWhite:
14376         if (WhiteOnMove(forwardMostMove)) {
14377             DisplayError(_("Wait until your turn"), 0);
14378             return;
14379         }
14380         break;
14381       case BeginningOfGame:
14382       case MachinePlaysBlack:
14383         if (!WhiteOnMove(forwardMostMove)) {
14384             DisplayError(_("Wait until your turn"), 0);
14385             return;
14386         }
14387         break;
14388       case EditPosition:
14389         EditPositionDone(TRUE);
14390         break;
14391       case TwoMachinesPlay:
14392         return;
14393       default:
14394         break;
14395     }
14396     SendToProgram("bk\n", &first);
14397     bookOutput[0] = NULLCHAR;
14398     bookRequested = TRUE;
14399 }
14400
14401 void
14402 AboutGameEvent()
14403 {
14404     char *tags = PGNTags(&gameInfo);
14405     TagsPopUp(tags, CmailMsg());
14406     free(tags);
14407 }
14408
14409 /* end button procedures */
14410
14411 void
14412 PrintPosition(fp, move)
14413      FILE *fp;
14414      int move;
14415 {
14416     int i, j;
14417
14418     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14419         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14420             char c = PieceToChar(boards[move][i][j]);
14421             fputc(c == 'x' ? '.' : c, fp);
14422             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14423         }
14424     }
14425     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14426       fprintf(fp, "white to play\n");
14427     else
14428       fprintf(fp, "black to play\n");
14429 }
14430
14431 void
14432 PrintOpponents(fp)
14433      FILE *fp;
14434 {
14435     if (gameInfo.white != NULL) {
14436         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14437     } else {
14438         fprintf(fp, "\n");
14439     }
14440 }
14441
14442 /* Find last component of program's own name, using some heuristics */
14443 void
14444 TidyProgramName(prog, host, buf)
14445      char *prog, *host, buf[MSG_SIZ];
14446 {
14447     char *p, *q;
14448     int local = (strcmp(host, "localhost") == 0);
14449     while (!local && (p = strchr(prog, ';')) != NULL) {
14450         p++;
14451         while (*p == ' ') p++;
14452         prog = p;
14453     }
14454     if (*prog == '"' || *prog == '\'') {
14455         q = strchr(prog + 1, *prog);
14456     } else {
14457         q = strchr(prog, ' ');
14458     }
14459     if (q == NULL) q = prog + strlen(prog);
14460     p = q;
14461     while (p >= prog && *p != '/' && *p != '\\') p--;
14462     p++;
14463     if(p == prog && *p == '"') p++;
14464     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14465     memcpy(buf, p, q - p);
14466     buf[q - p] = NULLCHAR;
14467     if (!local) {
14468         strcat(buf, "@");
14469         strcat(buf, host);
14470     }
14471 }
14472
14473 char *
14474 TimeControlTagValue()
14475 {
14476     char buf[MSG_SIZ];
14477     if (!appData.clockMode) {
14478       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14479     } else if (movesPerSession > 0) {
14480       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14481     } else if (timeIncrement == 0) {
14482       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14483     } else {
14484       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14485     }
14486     return StrSave(buf);
14487 }
14488
14489 void
14490 SetGameInfo()
14491 {
14492     /* This routine is used only for certain modes */
14493     VariantClass v = gameInfo.variant;
14494     ChessMove r = GameUnfinished;
14495     char *p = NULL;
14496
14497     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14498         r = gameInfo.result;
14499         p = gameInfo.resultDetails;
14500         gameInfo.resultDetails = NULL;
14501     }
14502     ClearGameInfo(&gameInfo);
14503     gameInfo.variant = v;
14504
14505     switch (gameMode) {
14506       case MachinePlaysWhite:
14507         gameInfo.event = StrSave( appData.pgnEventHeader );
14508         gameInfo.site = StrSave(HostName());
14509         gameInfo.date = PGNDate();
14510         gameInfo.round = StrSave("-");
14511         gameInfo.white = StrSave(first.tidy);
14512         gameInfo.black = StrSave(UserName());
14513         gameInfo.timeControl = TimeControlTagValue();
14514         break;
14515
14516       case MachinePlaysBlack:
14517         gameInfo.event = StrSave( appData.pgnEventHeader );
14518         gameInfo.site = StrSave(HostName());
14519         gameInfo.date = PGNDate();
14520         gameInfo.round = StrSave("-");
14521         gameInfo.white = StrSave(UserName());
14522         gameInfo.black = StrSave(first.tidy);
14523         gameInfo.timeControl = TimeControlTagValue();
14524         break;
14525
14526       case TwoMachinesPlay:
14527         gameInfo.event = StrSave( appData.pgnEventHeader );
14528         gameInfo.site = StrSave(HostName());
14529         gameInfo.date = PGNDate();
14530         if (roundNr > 0) {
14531             char buf[MSG_SIZ];
14532             snprintf(buf, MSG_SIZ, "%d", roundNr);
14533             gameInfo.round = StrSave(buf);
14534         } else {
14535             gameInfo.round = StrSave("-");
14536         }
14537         if (first.twoMachinesColor[0] == 'w') {
14538             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14539             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14540         } else {
14541             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14542             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14543         }
14544         gameInfo.timeControl = TimeControlTagValue();
14545         break;
14546
14547       case EditGame:
14548         gameInfo.event = StrSave("Edited game");
14549         gameInfo.site = StrSave(HostName());
14550         gameInfo.date = PGNDate();
14551         gameInfo.round = StrSave("-");
14552         gameInfo.white = StrSave("-");
14553         gameInfo.black = StrSave("-");
14554         gameInfo.result = r;
14555         gameInfo.resultDetails = p;
14556         break;
14557
14558       case EditPosition:
14559         gameInfo.event = StrSave("Edited position");
14560         gameInfo.site = StrSave(HostName());
14561         gameInfo.date = PGNDate();
14562         gameInfo.round = StrSave("-");
14563         gameInfo.white = StrSave("-");
14564         gameInfo.black = StrSave("-");
14565         break;
14566
14567       case IcsPlayingWhite:
14568       case IcsPlayingBlack:
14569       case IcsObserving:
14570       case IcsExamining:
14571         break;
14572
14573       case PlayFromGameFile:
14574         gameInfo.event = StrSave("Game from non-PGN file");
14575         gameInfo.site = StrSave(HostName());
14576         gameInfo.date = PGNDate();
14577         gameInfo.round = StrSave("-");
14578         gameInfo.white = StrSave("?");
14579         gameInfo.black = StrSave("?");
14580         break;
14581
14582       default:
14583         break;
14584     }
14585 }
14586
14587 void
14588 ReplaceComment(index, text)
14589      int index;
14590      char *text;
14591 {
14592     int len;
14593     char *p;
14594     float score;
14595
14596     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14597        pvInfoList[index-1].depth == len &&
14598        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14599        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14600     while (*text == '\n') text++;
14601     len = strlen(text);
14602     while (len > 0 && text[len - 1] == '\n') len--;
14603
14604     if (commentList[index] != NULL)
14605       free(commentList[index]);
14606
14607     if (len == 0) {
14608         commentList[index] = NULL;
14609         return;
14610     }
14611   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14612       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14613       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14614     commentList[index] = (char *) malloc(len + 2);
14615     strncpy(commentList[index], text, len);
14616     commentList[index][len] = '\n';
14617     commentList[index][len + 1] = NULLCHAR;
14618   } else {
14619     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14620     char *p;
14621     commentList[index] = (char *) malloc(len + 7);
14622     safeStrCpy(commentList[index], "{\n", 3);
14623     safeStrCpy(commentList[index]+2, text, len+1);
14624     commentList[index][len+2] = NULLCHAR;
14625     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14626     strcat(commentList[index], "\n}\n");
14627   }
14628 }
14629
14630 void
14631 CrushCRs(text)
14632      char *text;
14633 {
14634   char *p = text;
14635   char *q = text;
14636   char ch;
14637
14638   do {
14639     ch = *p++;
14640     if (ch == '\r') continue;
14641     *q++ = ch;
14642   } while (ch != '\0');
14643 }
14644
14645 void
14646 AppendComment(index, text, addBraces)
14647      int index;
14648      char *text;
14649      Boolean addBraces; // [HGM] braces: tells if we should add {}
14650 {
14651     int oldlen, len;
14652     char *old;
14653
14654 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14655     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14656
14657     CrushCRs(text);
14658     while (*text == '\n') text++;
14659     len = strlen(text);
14660     while (len > 0 && text[len - 1] == '\n') len--;
14661
14662     if (len == 0) return;
14663
14664     if (commentList[index] != NULL) {
14665       Boolean addClosingBrace = addBraces;
14666         old = commentList[index];
14667         oldlen = strlen(old);
14668         while(commentList[index][oldlen-1] ==  '\n')
14669           commentList[index][--oldlen] = NULLCHAR;
14670         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14671         safeStrCpy(commentList[index], old, oldlen + len + 6);
14672         free(old);
14673         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14674         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14675           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14676           while (*text == '\n') { text++; len--; }
14677           commentList[index][--oldlen] = NULLCHAR;
14678       }
14679         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14680         else          strcat(commentList[index], "\n");
14681         strcat(commentList[index], text);
14682         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14683         else          strcat(commentList[index], "\n");
14684     } else {
14685         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14686         if(addBraces)
14687           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14688         else commentList[index][0] = NULLCHAR;
14689         strcat(commentList[index], text);
14690         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14691         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14692     }
14693 }
14694
14695 static char * FindStr( char * text, char * sub_text )
14696 {
14697     char * result = strstr( text, sub_text );
14698
14699     if( result != NULL ) {
14700         result += strlen( sub_text );
14701     }
14702
14703     return result;
14704 }
14705
14706 /* [AS] Try to extract PV info from PGN comment */
14707 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14708 char *GetInfoFromComment( int index, char * text )
14709 {
14710     char * sep = text, *p;
14711
14712     if( text != NULL && index > 0 ) {
14713         int score = 0;
14714         int depth = 0;
14715         int time = -1, sec = 0, deci;
14716         char * s_eval = FindStr( text, "[%eval " );
14717         char * s_emt = FindStr( text, "[%emt " );
14718
14719         if( s_eval != NULL || s_emt != NULL ) {
14720             /* New style */
14721             char delim;
14722
14723             if( s_eval != NULL ) {
14724                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14725                     return text;
14726                 }
14727
14728                 if( delim != ']' ) {
14729                     return text;
14730                 }
14731             }
14732
14733             if( s_emt != NULL ) {
14734             }
14735                 return text;
14736         }
14737         else {
14738             /* We expect something like: [+|-]nnn.nn/dd */
14739             int score_lo = 0;
14740
14741             if(*text != '{') return text; // [HGM] braces: must be normal comment
14742
14743             sep = strchr( text, '/' );
14744             if( sep == NULL || sep < (text+4) ) {
14745                 return text;
14746             }
14747
14748             p = text;
14749             if(p[1] == '(') { // comment starts with PV
14750                p = strchr(p, ')'); // locate end of PV
14751                if(p == NULL || sep < p+5) return text;
14752                // at this point we have something like "{(.*) +0.23/6 ..."
14753                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14754                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14755                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14756             }
14757             time = -1; sec = -1; deci = -1;
14758             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14759                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14760                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14761                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14762                 return text;
14763             }
14764
14765             if( score_lo < 0 || score_lo >= 100 ) {
14766                 return text;
14767             }
14768
14769             if(sec >= 0) time = 600*time + 10*sec; else
14770             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14771
14772             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14773
14774             /* [HGM] PV time: now locate end of PV info */
14775             while( *++sep >= '0' && *sep <= '9'); // strip depth
14776             if(time >= 0)
14777             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14778             if(sec >= 0)
14779             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14780             if(deci >= 0)
14781             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14782             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14783         }
14784
14785         if( depth <= 0 ) {
14786             return text;
14787         }
14788
14789         if( time < 0 ) {
14790             time = -1;
14791         }
14792
14793         pvInfoList[index-1].depth = depth;
14794         pvInfoList[index-1].score = score;
14795         pvInfoList[index-1].time  = 10*time; // centi-sec
14796         if(*sep == '}') *sep = 0; else *--sep = '{';
14797         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14798     }
14799     return sep;
14800 }
14801
14802 void
14803 SendToProgram(message, cps)
14804      char *message;
14805      ChessProgramState *cps;
14806 {
14807     int count, outCount, error;
14808     char buf[MSG_SIZ];
14809
14810     if (cps->pr == NULL) return;
14811     Attention(cps);
14812
14813     if (appData.debugMode) {
14814         TimeMark now;
14815         GetTimeMark(&now);
14816         fprintf(debugFP, "%ld >%-6s: %s",
14817                 SubtractTimeMarks(&now, &programStartTime),
14818                 cps->which, message);
14819     }
14820
14821     count = strlen(message);
14822     outCount = OutputToProcess(cps->pr, message, count, &error);
14823     if (outCount < count && !exiting
14824                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14825       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14826       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14827         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14828             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14829                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14830                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14831                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14832             } else {
14833                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14834                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14835                 gameInfo.result = res;
14836             }
14837             gameInfo.resultDetails = StrSave(buf);
14838         }
14839         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14840         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14841     }
14842 }
14843
14844 void
14845 ReceiveFromProgram(isr, closure, message, count, error)
14846      InputSourceRef isr;
14847      VOIDSTAR closure;
14848      char *message;
14849      int count;
14850      int error;
14851 {
14852     char *end_str;
14853     char buf[MSG_SIZ];
14854     ChessProgramState *cps = (ChessProgramState *)closure;
14855
14856     if (isr != cps->isr) return; /* Killed intentionally */
14857     if (count <= 0) {
14858         if (count == 0) {
14859             RemoveInputSource(cps->isr);
14860             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14861             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14862                     _(cps->which), cps->program);
14863         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14864                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14865                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14866                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14867                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14868                 } else {
14869                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14870                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14871                     gameInfo.result = res;
14872                 }
14873                 gameInfo.resultDetails = StrSave(buf);
14874             }
14875             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14876             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14877         } else {
14878             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14879                     _(cps->which), cps->program);
14880             RemoveInputSource(cps->isr);
14881
14882             /* [AS] Program is misbehaving badly... kill it */
14883             if( count == -2 ) {
14884                 DestroyChildProcess( cps->pr, 9 );
14885                 cps->pr = NoProc;
14886             }
14887
14888             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14889         }
14890         return;
14891     }
14892
14893     if ((end_str = strchr(message, '\r')) != NULL)
14894       *end_str = NULLCHAR;
14895     if ((end_str = strchr(message, '\n')) != NULL)
14896       *end_str = NULLCHAR;
14897
14898     if (appData.debugMode) {
14899         TimeMark now; int print = 1;
14900         char *quote = ""; char c; int i;
14901
14902         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14903                 char start = message[0];
14904                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14905                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14906                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14907                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14908                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14909                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14910                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14911                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14912                    sscanf(message, "hint: %c", &c)!=1 && 
14913                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14914                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14915                     print = (appData.engineComments >= 2);
14916                 }
14917                 message[0] = start; // restore original message
14918         }
14919         if(print) {
14920                 GetTimeMark(&now);
14921                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14922                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14923                         quote,
14924                         message);
14925         }
14926     }
14927
14928     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14929     if (appData.icsEngineAnalyze) {
14930         if (strstr(message, "whisper") != NULL ||
14931              strstr(message, "kibitz") != NULL ||
14932             strstr(message, "tellics") != NULL) return;
14933     }
14934
14935     HandleMachineMove(message, cps);
14936 }
14937
14938
14939 void
14940 SendTimeControl(cps, mps, tc, inc, sd, st)
14941      ChessProgramState *cps;
14942      int mps, inc, sd, st;
14943      long tc;
14944 {
14945     char buf[MSG_SIZ];
14946     int seconds;
14947
14948     if( timeControl_2 > 0 ) {
14949         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14950             tc = timeControl_2;
14951         }
14952     }
14953     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14954     inc /= cps->timeOdds;
14955     st  /= cps->timeOdds;
14956
14957     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14958
14959     if (st > 0) {
14960       /* Set exact time per move, normally using st command */
14961       if (cps->stKludge) {
14962         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14963         seconds = st % 60;
14964         if (seconds == 0) {
14965           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14966         } else {
14967           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14968         }
14969       } else {
14970         snprintf(buf, MSG_SIZ, "st %d\n", st);
14971       }
14972     } else {
14973       /* Set conventional or incremental time control, using level command */
14974       if (seconds == 0) {
14975         /* Note old gnuchess bug -- minutes:seconds used to not work.
14976            Fixed in later versions, but still avoid :seconds
14977            when seconds is 0. */
14978         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14979       } else {
14980         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14981                  seconds, inc/1000.);
14982       }
14983     }
14984     SendToProgram(buf, cps);
14985
14986     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14987     /* Orthogonally, limit search to given depth */
14988     if (sd > 0) {
14989       if (cps->sdKludge) {
14990         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14991       } else {
14992         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14993       }
14994       SendToProgram(buf, cps);
14995     }
14996
14997     if(cps->nps >= 0) { /* [HGM] nps */
14998         if(cps->supportsNPS == FALSE)
14999           cps->nps = -1; // don't use if engine explicitly says not supported!
15000         else {
15001           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15002           SendToProgram(buf, cps);
15003         }
15004     }
15005 }
15006
15007 ChessProgramState *WhitePlayer()
15008 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15009 {
15010     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15011        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15012         return &second;
15013     return &first;
15014 }
15015
15016 void
15017 SendTimeRemaining(cps, machineWhite)
15018      ChessProgramState *cps;
15019      int /*boolean*/ machineWhite;
15020 {
15021     char message[MSG_SIZ];
15022     long time, otime;
15023
15024     /* Note: this routine must be called when the clocks are stopped
15025        or when they have *just* been set or switched; otherwise
15026        it will be off by the time since the current tick started.
15027     */
15028     if (machineWhite) {
15029         time = whiteTimeRemaining / 10;
15030         otime = blackTimeRemaining / 10;
15031     } else {
15032         time = blackTimeRemaining / 10;
15033         otime = whiteTimeRemaining / 10;
15034     }
15035     /* [HGM] translate opponent's time by time-odds factor */
15036     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15037     if (appData.debugMode) {
15038         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15039     }
15040
15041     if (time <= 0) time = 1;
15042     if (otime <= 0) otime = 1;
15043
15044     snprintf(message, MSG_SIZ, "time %ld\n", time);
15045     SendToProgram(message, cps);
15046
15047     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15048     SendToProgram(message, cps);
15049 }
15050
15051 int
15052 BoolFeature(p, name, loc, cps)
15053      char **p;
15054      char *name;
15055      int *loc;
15056      ChessProgramState *cps;
15057 {
15058   char buf[MSG_SIZ];
15059   int len = strlen(name);
15060   int val;
15061
15062   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15063     (*p) += len + 1;
15064     sscanf(*p, "%d", &val);
15065     *loc = (val != 0);
15066     while (**p && **p != ' ')
15067       (*p)++;
15068     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15069     SendToProgram(buf, cps);
15070     return TRUE;
15071   }
15072   return FALSE;
15073 }
15074
15075 int
15076 IntFeature(p, name, loc, cps)
15077      char **p;
15078      char *name;
15079      int *loc;
15080      ChessProgramState *cps;
15081 {
15082   char buf[MSG_SIZ];
15083   int len = strlen(name);
15084   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15085     (*p) += len + 1;
15086     sscanf(*p, "%d", loc);
15087     while (**p && **p != ' ') (*p)++;
15088     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15089     SendToProgram(buf, cps);
15090     return TRUE;
15091   }
15092   return FALSE;
15093 }
15094
15095 int
15096 StringFeature(p, name, loc, cps)
15097      char **p;
15098      char *name;
15099      char loc[];
15100      ChessProgramState *cps;
15101 {
15102   char buf[MSG_SIZ];
15103   int len = strlen(name);
15104   if (strncmp((*p), name, len) == 0
15105       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15106     (*p) += len + 2;
15107     sscanf(*p, "%[^\"]", loc);
15108     while (**p && **p != '\"') (*p)++;
15109     if (**p == '\"') (*p)++;
15110     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15111     SendToProgram(buf, cps);
15112     return TRUE;
15113   }
15114   return FALSE;
15115 }
15116
15117 int
15118 ParseOption(Option *opt, ChessProgramState *cps)
15119 // [HGM] options: process the string that defines an engine option, and determine
15120 // name, type, default value, and allowed value range
15121 {
15122         char *p, *q, buf[MSG_SIZ];
15123         int n, min = (-1)<<31, max = 1<<31, def;
15124
15125         if(p = strstr(opt->name, " -spin ")) {
15126             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15127             if(max < min) max = min; // enforce consistency
15128             if(def < min) def = min;
15129             if(def > max) def = max;
15130             opt->value = def;
15131             opt->min = min;
15132             opt->max = max;
15133             opt->type = Spin;
15134         } else if((p = strstr(opt->name, " -slider "))) {
15135             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15136             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15137             if(max < min) max = min; // enforce consistency
15138             if(def < min) def = min;
15139             if(def > max) def = max;
15140             opt->value = def;
15141             opt->min = min;
15142             opt->max = max;
15143             opt->type = Spin; // Slider;
15144         } else if((p = strstr(opt->name, " -string "))) {
15145             opt->textValue = p+9;
15146             opt->type = TextBox;
15147         } else if((p = strstr(opt->name, " -file "))) {
15148             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15149             opt->textValue = p+7;
15150             opt->type = FileName; // FileName;
15151         } else if((p = strstr(opt->name, " -path "))) {
15152             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15153             opt->textValue = p+7;
15154             opt->type = PathName; // PathName;
15155         } else if(p = strstr(opt->name, " -check ")) {
15156             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15157             opt->value = (def != 0);
15158             opt->type = CheckBox;
15159         } else if(p = strstr(opt->name, " -combo ")) {
15160             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15161             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15162             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15163             opt->value = n = 0;
15164             while(q = StrStr(q, " /// ")) {
15165                 n++; *q = 0;    // count choices, and null-terminate each of them
15166                 q += 5;
15167                 if(*q == '*') { // remember default, which is marked with * prefix
15168                     q++;
15169                     opt->value = n;
15170                 }
15171                 cps->comboList[cps->comboCnt++] = q;
15172             }
15173             cps->comboList[cps->comboCnt++] = NULL;
15174             opt->max = n + 1;
15175             opt->type = ComboBox;
15176         } else if(p = strstr(opt->name, " -button")) {
15177             opt->type = Button;
15178         } else if(p = strstr(opt->name, " -save")) {
15179             opt->type = SaveButton;
15180         } else return FALSE;
15181         *p = 0; // terminate option name
15182         // now look if the command-line options define a setting for this engine option.
15183         if(cps->optionSettings && cps->optionSettings[0])
15184             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15185         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15186           snprintf(buf, MSG_SIZ, "option %s", p);
15187                 if(p = strstr(buf, ",")) *p = 0;
15188                 if(q = strchr(buf, '=')) switch(opt->type) {
15189                     case ComboBox:
15190                         for(n=0; n<opt->max; n++)
15191                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15192                         break;
15193                     case TextBox:
15194                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15195                         break;
15196                     case Spin:
15197                     case CheckBox:
15198                         opt->value = atoi(q+1);
15199                     default:
15200                         break;
15201                 }
15202                 strcat(buf, "\n");
15203                 SendToProgram(buf, cps);
15204         }
15205         return TRUE;
15206 }
15207
15208 void
15209 FeatureDone(cps, val)
15210      ChessProgramState* cps;
15211      int val;
15212 {
15213   DelayedEventCallback cb = GetDelayedEvent();
15214   if ((cb == InitBackEnd3 && cps == &first) ||
15215       (cb == SettingsMenuIfReady && cps == &second) ||
15216       (cb == LoadEngine) ||
15217       (cb == TwoMachinesEventIfReady)) {
15218     CancelDelayedEvent();
15219     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15220   }
15221   cps->initDone = val;
15222 }
15223
15224 /* Parse feature command from engine */
15225 void
15226 ParseFeatures(args, cps)
15227      char* args;
15228      ChessProgramState *cps;
15229 {
15230   char *p = args;
15231   char *q;
15232   int val;
15233   char buf[MSG_SIZ];
15234
15235   for (;;) {
15236     while (*p == ' ') p++;
15237     if (*p == NULLCHAR) return;
15238
15239     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15240     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15241     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15242     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15243     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15244     if (BoolFeature(&p, "reuse", &val, cps)) {
15245       /* Engine can disable reuse, but can't enable it if user said no */
15246       if (!val) cps->reuse = FALSE;
15247       continue;
15248     }
15249     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15250     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15251       if (gameMode == TwoMachinesPlay) {
15252         DisplayTwoMachinesTitle();
15253       } else {
15254         DisplayTitle("");
15255       }
15256       continue;
15257     }
15258     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15259     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15260     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15261     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15262     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15263     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15264     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15265     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15266     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15267     if (IntFeature(&p, "done", &val, cps)) {
15268       FeatureDone(cps, val);
15269       continue;
15270     }
15271     /* Added by Tord: */
15272     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15273     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15274     /* End of additions by Tord */
15275
15276     /* [HGM] added features: */
15277     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15278     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15279     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15280     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15281     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15282     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15283     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15284         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15285           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15286             SendToProgram(buf, cps);
15287             continue;
15288         }
15289         if(cps->nrOptions >= MAX_OPTIONS) {
15290             cps->nrOptions--;
15291             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15292             DisplayError(buf, 0);
15293         }
15294         continue;
15295     }
15296     /* End of additions by HGM */
15297
15298     /* unknown feature: complain and skip */
15299     q = p;
15300     while (*q && *q != '=') q++;
15301     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15302     SendToProgram(buf, cps);
15303     p = q;
15304     if (*p == '=') {
15305       p++;
15306       if (*p == '\"') {
15307         p++;
15308         while (*p && *p != '\"') p++;
15309         if (*p == '\"') p++;
15310       } else {
15311         while (*p && *p != ' ') p++;
15312       }
15313     }
15314   }
15315
15316 }
15317
15318 void
15319 PeriodicUpdatesEvent(newState)
15320      int newState;
15321 {
15322     if (newState == appData.periodicUpdates)
15323       return;
15324
15325     appData.periodicUpdates=newState;
15326
15327     /* Display type changes, so update it now */
15328 //    DisplayAnalysis();
15329
15330     /* Get the ball rolling again... */
15331     if (newState) {
15332         AnalysisPeriodicEvent(1);
15333         StartAnalysisClock();
15334     }
15335 }
15336
15337 void
15338 PonderNextMoveEvent(newState)
15339      int newState;
15340 {
15341     if (newState == appData.ponderNextMove) return;
15342     if (gameMode == EditPosition) EditPositionDone(TRUE);
15343     if (newState) {
15344         SendToProgram("hard\n", &first);
15345         if (gameMode == TwoMachinesPlay) {
15346             SendToProgram("hard\n", &second);
15347         }
15348     } else {
15349         SendToProgram("easy\n", &first);
15350         thinkOutput[0] = NULLCHAR;
15351         if (gameMode == TwoMachinesPlay) {
15352             SendToProgram("easy\n", &second);
15353         }
15354     }
15355     appData.ponderNextMove = newState;
15356 }
15357
15358 void
15359 NewSettingEvent(option, feature, command, value)
15360      char *command;
15361      int option, value, *feature;
15362 {
15363     char buf[MSG_SIZ];
15364
15365     if (gameMode == EditPosition) EditPositionDone(TRUE);
15366     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15367     if(feature == NULL || *feature) SendToProgram(buf, &first);
15368     if (gameMode == TwoMachinesPlay) {
15369         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15370     }
15371 }
15372
15373 void
15374 ShowThinkingEvent()
15375 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15376 {
15377     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15378     int newState = appData.showThinking
15379         // [HGM] thinking: other features now need thinking output as well
15380         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15381
15382     if (oldState == newState) return;
15383     oldState = newState;
15384     if (gameMode == EditPosition) EditPositionDone(TRUE);
15385     if (oldState) {
15386         SendToProgram("post\n", &first);
15387         if (gameMode == TwoMachinesPlay) {
15388             SendToProgram("post\n", &second);
15389         }
15390     } else {
15391         SendToProgram("nopost\n", &first);
15392         thinkOutput[0] = NULLCHAR;
15393         if (gameMode == TwoMachinesPlay) {
15394             SendToProgram("nopost\n", &second);
15395         }
15396     }
15397 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15398 }
15399
15400 void
15401 AskQuestionEvent(title, question, replyPrefix, which)
15402      char *title; char *question; char *replyPrefix; char *which;
15403 {
15404   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15405   if (pr == NoProc) return;
15406   AskQuestion(title, question, replyPrefix, pr);
15407 }
15408
15409 void
15410 TypeInEvent(char firstChar)
15411 {
15412     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15413         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15414         gameMode == AnalyzeMode || gameMode == EditGame || 
15415         gameMode == EditPosition || gameMode == IcsExamining ||
15416         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15417         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15418                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15419                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15420         gameMode == Training) PopUpMoveDialog(firstChar);
15421 }
15422
15423 void
15424 TypeInDoneEvent(char *move)
15425 {
15426         Board board;
15427         int n, fromX, fromY, toX, toY;
15428         char promoChar;
15429         ChessMove moveType;
15430
15431         // [HGM] FENedit
15432         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15433                 EditPositionPasteFEN(move);
15434                 return;
15435         }
15436         // [HGM] movenum: allow move number to be typed in any mode
15437         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15438           ToNrEvent(2*n-1);
15439           return;
15440         }
15441
15442       if (gameMode != EditGame && currentMove != forwardMostMove && 
15443         gameMode != Training) {
15444         DisplayMoveError(_("Displayed move is not current"));
15445       } else {
15446         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15447           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15448         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15449         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15450           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15451           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15452         } else {
15453           DisplayMoveError(_("Could not parse move"));
15454         }
15455       }
15456 }
15457
15458 void
15459 DisplayMove(moveNumber)
15460      int moveNumber;
15461 {
15462     char message[MSG_SIZ];
15463     char res[MSG_SIZ];
15464     char cpThinkOutput[MSG_SIZ];
15465
15466     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15467
15468     if (moveNumber == forwardMostMove - 1 ||
15469         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15470
15471         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15472
15473         if (strchr(cpThinkOutput, '\n')) {
15474             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15475         }
15476     } else {
15477         *cpThinkOutput = NULLCHAR;
15478     }
15479
15480     /* [AS] Hide thinking from human user */
15481     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15482         *cpThinkOutput = NULLCHAR;
15483         if( thinkOutput[0] != NULLCHAR ) {
15484             int i;
15485
15486             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15487                 cpThinkOutput[i] = '.';
15488             }
15489             cpThinkOutput[i] = NULLCHAR;
15490             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15491         }
15492     }
15493
15494     if (moveNumber == forwardMostMove - 1 &&
15495         gameInfo.resultDetails != NULL) {
15496         if (gameInfo.resultDetails[0] == NULLCHAR) {
15497           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15498         } else {
15499           snprintf(res, MSG_SIZ, " {%s} %s",
15500                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15501         }
15502     } else {
15503         res[0] = NULLCHAR;
15504     }
15505
15506     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15507         DisplayMessage(res, cpThinkOutput);
15508     } else {
15509       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15510                 WhiteOnMove(moveNumber) ? " " : ".. ",
15511                 parseList[moveNumber], res);
15512         DisplayMessage(message, cpThinkOutput);
15513     }
15514 }
15515
15516 void
15517 DisplayComment(moveNumber, text)
15518      int moveNumber;
15519      char *text;
15520 {
15521     char title[MSG_SIZ];
15522
15523     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15524       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15525     } else {
15526       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15527               WhiteOnMove(moveNumber) ? " " : ".. ",
15528               parseList[moveNumber]);
15529     }
15530     if (text != NULL && (appData.autoDisplayComment || commentUp))
15531         CommentPopUp(title, text);
15532 }
15533
15534 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15535  * might be busy thinking or pondering.  It can be omitted if your
15536  * gnuchess is configured to stop thinking immediately on any user
15537  * input.  However, that gnuchess feature depends on the FIONREAD
15538  * ioctl, which does not work properly on some flavors of Unix.
15539  */
15540 void
15541 Attention(cps)
15542      ChessProgramState *cps;
15543 {
15544 #if ATTENTION
15545     if (!cps->useSigint) return;
15546     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15547     switch (gameMode) {
15548       case MachinePlaysWhite:
15549       case MachinePlaysBlack:
15550       case TwoMachinesPlay:
15551       case IcsPlayingWhite:
15552       case IcsPlayingBlack:
15553       case AnalyzeMode:
15554       case AnalyzeFile:
15555         /* Skip if we know it isn't thinking */
15556         if (!cps->maybeThinking) return;
15557         if (appData.debugMode)
15558           fprintf(debugFP, "Interrupting %s\n", cps->which);
15559         InterruptChildProcess(cps->pr);
15560         cps->maybeThinking = FALSE;
15561         break;
15562       default:
15563         break;
15564     }
15565 #endif /*ATTENTION*/
15566 }
15567
15568 int
15569 CheckFlags()
15570 {
15571     if (whiteTimeRemaining <= 0) {
15572         if (!whiteFlag) {
15573             whiteFlag = TRUE;
15574             if (appData.icsActive) {
15575                 if (appData.autoCallFlag &&
15576                     gameMode == IcsPlayingBlack && !blackFlag) {
15577                   SendToICS(ics_prefix);
15578                   SendToICS("flag\n");
15579                 }
15580             } else {
15581                 if (blackFlag) {
15582                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15583                 } else {
15584                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15585                     if (appData.autoCallFlag) {
15586                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15587                         return TRUE;
15588                     }
15589                 }
15590             }
15591         }
15592     }
15593     if (blackTimeRemaining <= 0) {
15594         if (!blackFlag) {
15595             blackFlag = TRUE;
15596             if (appData.icsActive) {
15597                 if (appData.autoCallFlag &&
15598                     gameMode == IcsPlayingWhite && !whiteFlag) {
15599                   SendToICS(ics_prefix);
15600                   SendToICS("flag\n");
15601                 }
15602             } else {
15603                 if (whiteFlag) {
15604                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15605                 } else {
15606                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15607                     if (appData.autoCallFlag) {
15608                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15609                         return TRUE;
15610                     }
15611                 }
15612             }
15613         }
15614     }
15615     return FALSE;
15616 }
15617
15618 void
15619 CheckTimeControl()
15620 {
15621     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15622         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15623
15624     /*
15625      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15626      */
15627     if ( !WhiteOnMove(forwardMostMove) ) {
15628         /* White made time control */
15629         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15630         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15631         /* [HGM] time odds: correct new time quota for time odds! */
15632                                             / WhitePlayer()->timeOdds;
15633         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15634     } else {
15635         lastBlack -= blackTimeRemaining;
15636         /* Black made time control */
15637         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15638                                             / WhitePlayer()->other->timeOdds;
15639         lastWhite = whiteTimeRemaining;
15640     }
15641 }
15642
15643 void
15644 DisplayBothClocks()
15645 {
15646     int wom = gameMode == EditPosition ?
15647       !blackPlaysFirst : WhiteOnMove(currentMove);
15648     DisplayWhiteClock(whiteTimeRemaining, wom);
15649     DisplayBlackClock(blackTimeRemaining, !wom);
15650 }
15651
15652
15653 /* Timekeeping seems to be a portability nightmare.  I think everyone
15654    has ftime(), but I'm really not sure, so I'm including some ifdefs
15655    to use other calls if you don't.  Clocks will be less accurate if
15656    you have neither ftime nor gettimeofday.
15657 */
15658
15659 /* VS 2008 requires the #include outside of the function */
15660 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15661 #include <sys/timeb.h>
15662 #endif
15663
15664 /* Get the current time as a TimeMark */
15665 void
15666 GetTimeMark(tm)
15667      TimeMark *tm;
15668 {
15669 #if HAVE_GETTIMEOFDAY
15670
15671     struct timeval timeVal;
15672     struct timezone timeZone;
15673
15674     gettimeofday(&timeVal, &timeZone);
15675     tm->sec = (long) timeVal.tv_sec;
15676     tm->ms = (int) (timeVal.tv_usec / 1000L);
15677
15678 #else /*!HAVE_GETTIMEOFDAY*/
15679 #if HAVE_FTIME
15680
15681 // include <sys/timeb.h> / moved to just above start of function
15682     struct timeb timeB;
15683
15684     ftime(&timeB);
15685     tm->sec = (long) timeB.time;
15686     tm->ms = (int) timeB.millitm;
15687
15688 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15689     tm->sec = (long) time(NULL);
15690     tm->ms = 0;
15691 #endif
15692 #endif
15693 }
15694
15695 /* Return the difference in milliseconds between two
15696    time marks.  We assume the difference will fit in a long!
15697 */
15698 long
15699 SubtractTimeMarks(tm2, tm1)
15700      TimeMark *tm2, *tm1;
15701 {
15702     return 1000L*(tm2->sec - tm1->sec) +
15703            (long) (tm2->ms - tm1->ms);
15704 }
15705
15706
15707 /*
15708  * Code to manage the game clocks.
15709  *
15710  * In tournament play, black starts the clock and then white makes a move.
15711  * We give the human user a slight advantage if he is playing white---the
15712  * clocks don't run until he makes his first move, so it takes zero time.
15713  * Also, we don't account for network lag, so we could get out of sync
15714  * with GNU Chess's clock -- but then, referees are always right.
15715  */
15716
15717 static TimeMark tickStartTM;
15718 static long intendedTickLength;
15719
15720 long
15721 NextTickLength(timeRemaining)
15722      long timeRemaining;
15723 {
15724     long nominalTickLength, nextTickLength;
15725
15726     if (timeRemaining > 0L && timeRemaining <= 10000L)
15727       nominalTickLength = 100L;
15728     else
15729       nominalTickLength = 1000L;
15730     nextTickLength = timeRemaining % nominalTickLength;
15731     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15732
15733     return nextTickLength;
15734 }
15735
15736 /* Adjust clock one minute up or down */
15737 void
15738 AdjustClock(Boolean which, int dir)
15739 {
15740     if(which) blackTimeRemaining += 60000*dir;
15741     else      whiteTimeRemaining += 60000*dir;
15742     DisplayBothClocks();
15743 }
15744
15745 /* Stop clocks and reset to a fresh time control */
15746 void
15747 ResetClocks()
15748 {
15749     (void) StopClockTimer();
15750     if (appData.icsActive) {
15751         whiteTimeRemaining = blackTimeRemaining = 0;
15752     } else if (searchTime) {
15753         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15754         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15755     } else { /* [HGM] correct new time quote for time odds */
15756         whiteTC = blackTC = fullTimeControlString;
15757         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15758         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15759     }
15760     if (whiteFlag || blackFlag) {
15761         DisplayTitle("");
15762         whiteFlag = blackFlag = FALSE;
15763     }
15764     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15765     DisplayBothClocks();
15766 }
15767
15768 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15769
15770 /* Decrement running clock by amount of time that has passed */
15771 void
15772 DecrementClocks()
15773 {
15774     long timeRemaining;
15775     long lastTickLength, fudge;
15776     TimeMark now;
15777
15778     if (!appData.clockMode) return;
15779     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15780
15781     GetTimeMark(&now);
15782
15783     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15784
15785     /* Fudge if we woke up a little too soon */
15786     fudge = intendedTickLength - lastTickLength;
15787     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15788
15789     if (WhiteOnMove(forwardMostMove)) {
15790         if(whiteNPS >= 0) lastTickLength = 0;
15791         timeRemaining = whiteTimeRemaining -= lastTickLength;
15792         if(timeRemaining < 0 && !appData.icsActive) {
15793             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15794             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15795                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15796                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15797             }
15798         }
15799         DisplayWhiteClock(whiteTimeRemaining - fudge,
15800                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15801     } else {
15802         if(blackNPS >= 0) lastTickLength = 0;
15803         timeRemaining = blackTimeRemaining -= lastTickLength;
15804         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15805             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15806             if(suddenDeath) {
15807                 blackStartMove = forwardMostMove;
15808                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15809             }
15810         }
15811         DisplayBlackClock(blackTimeRemaining - fudge,
15812                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15813     }
15814     if (CheckFlags()) return;
15815
15816     tickStartTM = now;
15817     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15818     StartClockTimer(intendedTickLength);
15819
15820     /* if the time remaining has fallen below the alarm threshold, sound the
15821      * alarm. if the alarm has sounded and (due to a takeback or time control
15822      * with increment) the time remaining has increased to a level above the
15823      * threshold, reset the alarm so it can sound again.
15824      */
15825
15826     if (appData.icsActive && appData.icsAlarm) {
15827
15828         /* make sure we are dealing with the user's clock */
15829         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15830                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15831            )) return;
15832
15833         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15834             alarmSounded = FALSE;
15835         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15836             PlayAlarmSound();
15837             alarmSounded = TRUE;
15838         }
15839     }
15840 }
15841
15842
15843 /* A player has just moved, so stop the previously running
15844    clock and (if in clock mode) start the other one.
15845    We redisplay both clocks in case we're in ICS mode, because
15846    ICS gives us an update to both clocks after every move.
15847    Note that this routine is called *after* forwardMostMove
15848    is updated, so the last fractional tick must be subtracted
15849    from the color that is *not* on move now.
15850 */
15851 void
15852 SwitchClocks(int newMoveNr)
15853 {
15854     long lastTickLength;
15855     TimeMark now;
15856     int flagged = FALSE;
15857
15858     GetTimeMark(&now);
15859
15860     if (StopClockTimer() && appData.clockMode) {
15861         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15862         if (!WhiteOnMove(forwardMostMove)) {
15863             if(blackNPS >= 0) lastTickLength = 0;
15864             blackTimeRemaining -= lastTickLength;
15865            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15866 //         if(pvInfoList[forwardMostMove].time == -1)
15867                  pvInfoList[forwardMostMove].time =               // use GUI time
15868                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15869         } else {
15870            if(whiteNPS >= 0) lastTickLength = 0;
15871            whiteTimeRemaining -= lastTickLength;
15872            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15873 //         if(pvInfoList[forwardMostMove].time == -1)
15874                  pvInfoList[forwardMostMove].time =
15875                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15876         }
15877         flagged = CheckFlags();
15878     }
15879     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15880     CheckTimeControl();
15881
15882     if (flagged || !appData.clockMode) return;
15883
15884     switch (gameMode) {
15885       case MachinePlaysBlack:
15886       case MachinePlaysWhite:
15887       case BeginningOfGame:
15888         if (pausing) return;
15889         break;
15890
15891       case EditGame:
15892       case PlayFromGameFile:
15893       case IcsExamining:
15894         return;
15895
15896       default:
15897         break;
15898     }
15899
15900     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15901         if(WhiteOnMove(forwardMostMove))
15902              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15903         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15904     }
15905
15906     tickStartTM = now;
15907     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15908       whiteTimeRemaining : blackTimeRemaining);
15909     StartClockTimer(intendedTickLength);
15910 }
15911
15912
15913 /* Stop both clocks */
15914 void
15915 StopClocks()
15916 {
15917     long lastTickLength;
15918     TimeMark now;
15919
15920     if (!StopClockTimer()) return;
15921     if (!appData.clockMode) return;
15922
15923     GetTimeMark(&now);
15924
15925     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15926     if (WhiteOnMove(forwardMostMove)) {
15927         if(whiteNPS >= 0) lastTickLength = 0;
15928         whiteTimeRemaining -= lastTickLength;
15929         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15930     } else {
15931         if(blackNPS >= 0) lastTickLength = 0;
15932         blackTimeRemaining -= lastTickLength;
15933         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15934     }
15935     CheckFlags();
15936 }
15937
15938 /* Start clock of player on move.  Time may have been reset, so
15939    if clock is already running, stop and restart it. */
15940 void
15941 StartClocks()
15942 {
15943     (void) StopClockTimer(); /* in case it was running already */
15944     DisplayBothClocks();
15945     if (CheckFlags()) return;
15946
15947     if (!appData.clockMode) return;
15948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15949
15950     GetTimeMark(&tickStartTM);
15951     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15952       whiteTimeRemaining : blackTimeRemaining);
15953
15954    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15955     whiteNPS = blackNPS = -1;
15956     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15957        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15958         whiteNPS = first.nps;
15959     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15960        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15961         blackNPS = first.nps;
15962     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15963         whiteNPS = second.nps;
15964     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15965         blackNPS = second.nps;
15966     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15967
15968     StartClockTimer(intendedTickLength);
15969 }
15970
15971 char *
15972 TimeString(ms)
15973      long ms;
15974 {
15975     long second, minute, hour, day;
15976     char *sign = "";
15977     static char buf[32];
15978
15979     if (ms > 0 && ms <= 9900) {
15980       /* convert milliseconds to tenths, rounding up */
15981       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15982
15983       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15984       return buf;
15985     }
15986
15987     /* convert milliseconds to seconds, rounding up */
15988     /* use floating point to avoid strangeness of integer division
15989        with negative dividends on many machines */
15990     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15991
15992     if (second < 0) {
15993         sign = "-";
15994         second = -second;
15995     }
15996
15997     day = second / (60 * 60 * 24);
15998     second = second % (60 * 60 * 24);
15999     hour = second / (60 * 60);
16000     second = second % (60 * 60);
16001     minute = second / 60;
16002     second = second % 60;
16003
16004     if (day > 0)
16005       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16006               sign, day, hour, minute, second);
16007     else if (hour > 0)
16008       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16009     else
16010       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16011
16012     return buf;
16013 }
16014
16015
16016 /*
16017  * This is necessary because some C libraries aren't ANSI C compliant yet.
16018  */
16019 char *
16020 StrStr(string, match)
16021      char *string, *match;
16022 {
16023     int i, length;
16024
16025     length = strlen(match);
16026
16027     for (i = strlen(string) - length; i >= 0; i--, string++)
16028       if (!strncmp(match, string, length))
16029         return string;
16030
16031     return NULL;
16032 }
16033
16034 char *
16035 StrCaseStr(string, match)
16036      char *string, *match;
16037 {
16038     int i, j, length;
16039
16040     length = strlen(match);
16041
16042     for (i = strlen(string) - length; i >= 0; i--, string++) {
16043         for (j = 0; j < length; j++) {
16044             if (ToLower(match[j]) != ToLower(string[j]))
16045               break;
16046         }
16047         if (j == length) return string;
16048     }
16049
16050     return NULL;
16051 }
16052
16053 #ifndef _amigados
16054 int
16055 StrCaseCmp(s1, s2)
16056      char *s1, *s2;
16057 {
16058     char c1, c2;
16059
16060     for (;;) {
16061         c1 = ToLower(*s1++);
16062         c2 = ToLower(*s2++);
16063         if (c1 > c2) return 1;
16064         if (c1 < c2) return -1;
16065         if (c1 == NULLCHAR) return 0;
16066     }
16067 }
16068
16069
16070 int
16071 ToLower(c)
16072      int c;
16073 {
16074     return isupper(c) ? tolower(c) : c;
16075 }
16076
16077
16078 int
16079 ToUpper(c)
16080      int c;
16081 {
16082     return islower(c) ? toupper(c) : c;
16083 }
16084 #endif /* !_amigados    */
16085
16086 char *
16087 StrSave(s)
16088      char *s;
16089 {
16090   char *ret;
16091
16092   if ((ret = (char *) malloc(strlen(s) + 1)))
16093     {
16094       safeStrCpy(ret, s, strlen(s)+1);
16095     }
16096   return ret;
16097 }
16098
16099 char *
16100 StrSavePtr(s, savePtr)
16101      char *s, **savePtr;
16102 {
16103     if (*savePtr) {
16104         free(*savePtr);
16105     }
16106     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16107       safeStrCpy(*savePtr, s, strlen(s)+1);
16108     }
16109     return(*savePtr);
16110 }
16111
16112 char *
16113 PGNDate()
16114 {
16115     time_t clock;
16116     struct tm *tm;
16117     char buf[MSG_SIZ];
16118
16119     clock = time((time_t *)NULL);
16120     tm = localtime(&clock);
16121     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16122             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16123     return StrSave(buf);
16124 }
16125
16126
16127 char *
16128 PositionToFEN(move, overrideCastling)
16129      int move;
16130      char *overrideCastling;
16131 {
16132     int i, j, fromX, fromY, toX, toY;
16133     int whiteToPlay;
16134     char buf[MSG_SIZ];
16135     char *p, *q;
16136     int emptycount;
16137     ChessSquare piece;
16138
16139     whiteToPlay = (gameMode == EditPosition) ?
16140       !blackPlaysFirst : (move % 2 == 0);
16141     p = buf;
16142
16143     /* Piece placement data */
16144     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16145         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16146         emptycount = 0;
16147         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16148             if (boards[move][i][j] == EmptySquare) {
16149                 emptycount++;
16150             } else { ChessSquare piece = boards[move][i][j];
16151                 if (emptycount > 0) {
16152                     if(emptycount<10) /* [HGM] can be >= 10 */
16153                         *p++ = '0' + emptycount;
16154                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16155                     emptycount = 0;
16156                 }
16157                 if(PieceToChar(piece) == '+') {
16158                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16159                     *p++ = '+';
16160                     piece = (ChessSquare)(DEMOTED piece);
16161                 }
16162                 *p++ = PieceToChar(piece);
16163                 if(p[-1] == '~') {
16164                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16165                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16166                     *p++ = '~';
16167                 }
16168             }
16169         }
16170         if (emptycount > 0) {
16171             if(emptycount<10) /* [HGM] can be >= 10 */
16172                 *p++ = '0' + emptycount;
16173             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16174             emptycount = 0;
16175         }
16176         *p++ = '/';
16177     }
16178     *(p - 1) = ' ';
16179
16180     /* [HGM] print Crazyhouse or Shogi holdings */
16181     if( gameInfo.holdingsWidth ) {
16182         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16183         q = p;
16184         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16185             piece = boards[move][i][BOARD_WIDTH-1];
16186             if( piece != EmptySquare )
16187               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16188                   *p++ = PieceToChar(piece);
16189         }
16190         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16191             piece = boards[move][BOARD_HEIGHT-i-1][0];
16192             if( piece != EmptySquare )
16193               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16194                   *p++ = PieceToChar(piece);
16195         }
16196
16197         if( q == p ) *p++ = '-';
16198         *p++ = ']';
16199         *p++ = ' ';
16200     }
16201
16202     /* Active color */
16203     *p++ = whiteToPlay ? 'w' : 'b';
16204     *p++ = ' ';
16205
16206   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16207     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16208   } else {
16209   if(nrCastlingRights) {
16210      q = p;
16211      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16212        /* [HGM] write directly from rights */
16213            if(boards[move][CASTLING][2] != NoRights &&
16214               boards[move][CASTLING][0] != NoRights   )
16215                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16216            if(boards[move][CASTLING][2] != NoRights &&
16217               boards[move][CASTLING][1] != NoRights   )
16218                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16219            if(boards[move][CASTLING][5] != NoRights &&
16220               boards[move][CASTLING][3] != NoRights   )
16221                 *p++ = boards[move][CASTLING][3] + AAA;
16222            if(boards[move][CASTLING][5] != NoRights &&
16223               boards[move][CASTLING][4] != NoRights   )
16224                 *p++ = boards[move][CASTLING][4] + AAA;
16225      } else {
16226
16227         /* [HGM] write true castling rights */
16228         if( nrCastlingRights == 6 ) {
16229             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16230                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16231             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16232                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16233             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16234                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16235             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16236                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16237         }
16238      }
16239      if (q == p) *p++ = '-'; /* No castling rights */
16240      *p++ = ' ';
16241   }
16242
16243   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16244      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16245     /* En passant target square */
16246     if (move > backwardMostMove) {
16247         fromX = moveList[move - 1][0] - AAA;
16248         fromY = moveList[move - 1][1] - ONE;
16249         toX = moveList[move - 1][2] - AAA;
16250         toY = moveList[move - 1][3] - ONE;
16251         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16252             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16253             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16254             fromX == toX) {
16255             /* 2-square pawn move just happened */
16256             *p++ = toX + AAA;
16257             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16258         } else {
16259             *p++ = '-';
16260         }
16261     } else if(move == backwardMostMove) {
16262         // [HGM] perhaps we should always do it like this, and forget the above?
16263         if((signed char)boards[move][EP_STATUS] >= 0) {
16264             *p++ = boards[move][EP_STATUS] + AAA;
16265             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16266         } else {
16267             *p++ = '-';
16268         }
16269     } else {
16270         *p++ = '-';
16271     }
16272     *p++ = ' ';
16273   }
16274   }
16275
16276     /* [HGM] find reversible plies */
16277     {   int i = 0, j=move;
16278
16279         if (appData.debugMode) { int k;
16280             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16281             for(k=backwardMostMove; k<=forwardMostMove; k++)
16282                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16283
16284         }
16285
16286         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16287         if( j == backwardMostMove ) i += initialRulePlies;
16288         sprintf(p, "%d ", i);
16289         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16290     }
16291     /* Fullmove number */
16292     sprintf(p, "%d", (move / 2) + 1);
16293
16294     return StrSave(buf);
16295 }
16296
16297 Boolean
16298 ParseFEN(board, blackPlaysFirst, fen)
16299     Board board;
16300      int *blackPlaysFirst;
16301      char *fen;
16302 {
16303     int i, j;
16304     char *p, c;
16305     int emptycount;
16306     ChessSquare piece;
16307
16308     p = fen;
16309
16310     /* [HGM] by default clear Crazyhouse holdings, if present */
16311     if(gameInfo.holdingsWidth) {
16312        for(i=0; i<BOARD_HEIGHT; i++) {
16313            board[i][0]             = EmptySquare; /* black holdings */
16314            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16315            board[i][1]             = (ChessSquare) 0; /* black counts */
16316            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16317        }
16318     }
16319
16320     /* Piece placement data */
16321     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16322         j = 0;
16323         for (;;) {
16324             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16325                 if (*p == '/') p++;
16326                 emptycount = gameInfo.boardWidth - j;
16327                 while (emptycount--)
16328                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16329                 break;
16330 #if(BOARD_FILES >= 10)
16331             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16332                 p++; emptycount=10;
16333                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16334                 while (emptycount--)
16335                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16336 #endif
16337             } else if (isdigit(*p)) {
16338                 emptycount = *p++ - '0';
16339                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16340                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16341                 while (emptycount--)
16342                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16343             } else if (*p == '+' || isalpha(*p)) {
16344                 if (j >= gameInfo.boardWidth) return FALSE;
16345                 if(*p=='+') {
16346                     piece = CharToPiece(*++p);
16347                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16348                     piece = (ChessSquare) (PROMOTED piece ); p++;
16349                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16350                 } else piece = CharToPiece(*p++);
16351
16352                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16353                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16354                     piece = (ChessSquare) (PROMOTED piece);
16355                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16356                     p++;
16357                 }
16358                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16359             } else {
16360                 return FALSE;
16361             }
16362         }
16363     }
16364     while (*p == '/' || *p == ' ') p++;
16365
16366     /* [HGM] look for Crazyhouse holdings here */
16367     while(*p==' ') p++;
16368     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16369         if(*p == '[') p++;
16370         if(*p == '-' ) p++; /* empty holdings */ else {
16371             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16372             /* if we would allow FEN reading to set board size, we would   */
16373             /* have to add holdings and shift the board read so far here   */
16374             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16375                 p++;
16376                 if((int) piece >= (int) BlackPawn ) {
16377                     i = (int)piece - (int)BlackPawn;
16378                     i = PieceToNumber((ChessSquare)i);
16379                     if( i >= gameInfo.holdingsSize ) return FALSE;
16380                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16381                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16382                 } else {
16383                     i = (int)piece - (int)WhitePawn;
16384                     i = PieceToNumber((ChessSquare)i);
16385                     if( i >= gameInfo.holdingsSize ) return FALSE;
16386                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16387                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16388                 }
16389             }
16390         }
16391         if(*p == ']') p++;
16392     }
16393
16394     while(*p == ' ') p++;
16395
16396     /* Active color */
16397     c = *p++;
16398     if(appData.colorNickNames) {
16399       if( c == appData.colorNickNames[0] ) c = 'w'; else
16400       if( c == appData.colorNickNames[1] ) c = 'b';
16401     }
16402     switch (c) {
16403       case 'w':
16404         *blackPlaysFirst = FALSE;
16405         break;
16406       case 'b':
16407         *blackPlaysFirst = TRUE;
16408         break;
16409       default:
16410         return FALSE;
16411     }
16412
16413     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16414     /* return the extra info in global variiables             */
16415
16416     /* set defaults in case FEN is incomplete */
16417     board[EP_STATUS] = EP_UNKNOWN;
16418     for(i=0; i<nrCastlingRights; i++ ) {
16419         board[CASTLING][i] =
16420             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16421     }   /* assume possible unless obviously impossible */
16422     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16423     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16424     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16425                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16426     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16427     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16428     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16429                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16430     FENrulePlies = 0;
16431
16432     while(*p==' ') p++;
16433     if(nrCastlingRights) {
16434       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16435           /* castling indicator present, so default becomes no castlings */
16436           for(i=0; i<nrCastlingRights; i++ ) {
16437                  board[CASTLING][i] = NoRights;
16438           }
16439       }
16440       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16441              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16442              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16443              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16444         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16445
16446         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16447             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16448             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16449         }
16450         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16451             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16452         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16453                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16454         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16455                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16456         switch(c) {
16457           case'K':
16458               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16459               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16460               board[CASTLING][2] = whiteKingFile;
16461               break;
16462           case'Q':
16463               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16464               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16465               board[CASTLING][2] = whiteKingFile;
16466               break;
16467           case'k':
16468               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16469               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16470               board[CASTLING][5] = blackKingFile;
16471               break;
16472           case'q':
16473               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16474               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16475               board[CASTLING][5] = blackKingFile;
16476           case '-':
16477               break;
16478           default: /* FRC castlings */
16479               if(c >= 'a') { /* black rights */
16480                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16481                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16482                   if(i == BOARD_RGHT) break;
16483                   board[CASTLING][5] = i;
16484                   c -= AAA;
16485                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16486                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16487                   if(c > i)
16488                       board[CASTLING][3] = c;
16489                   else
16490                       board[CASTLING][4] = c;
16491               } else { /* white rights */
16492                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16493                     if(board[0][i] == WhiteKing) break;
16494                   if(i == BOARD_RGHT) break;
16495                   board[CASTLING][2] = i;
16496                   c -= AAA - 'a' + 'A';
16497                   if(board[0][c] >= WhiteKing) break;
16498                   if(c > i)
16499                       board[CASTLING][0] = c;
16500                   else
16501                       board[CASTLING][1] = c;
16502               }
16503         }
16504       }
16505       for(i=0; i<nrCastlingRights; i++)
16506         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16507     if (appData.debugMode) {
16508         fprintf(debugFP, "FEN castling rights:");
16509         for(i=0; i<nrCastlingRights; i++)
16510         fprintf(debugFP, " %d", board[CASTLING][i]);
16511         fprintf(debugFP, "\n");
16512     }
16513
16514       while(*p==' ') p++;
16515     }
16516
16517     /* read e.p. field in games that know e.p. capture */
16518     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16519        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16520       if(*p=='-') {
16521         p++; board[EP_STATUS] = EP_NONE;
16522       } else {
16523          char c = *p++ - AAA;
16524
16525          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16526          if(*p >= '0' && *p <='9') p++;
16527          board[EP_STATUS] = c;
16528       }
16529     }
16530
16531
16532     if(sscanf(p, "%d", &i) == 1) {
16533         FENrulePlies = i; /* 50-move ply counter */
16534         /* (The move number is still ignored)    */
16535     }
16536
16537     return TRUE;
16538 }
16539
16540 void
16541 EditPositionPasteFEN(char *fen)
16542 {
16543   if (fen != NULL) {
16544     Board initial_position;
16545
16546     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16547       DisplayError(_("Bad FEN position in clipboard"), 0);
16548       return ;
16549     } else {
16550       int savedBlackPlaysFirst = blackPlaysFirst;
16551       EditPositionEvent();
16552       blackPlaysFirst = savedBlackPlaysFirst;
16553       CopyBoard(boards[0], initial_position);
16554       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16555       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16556       DisplayBothClocks();
16557       DrawPosition(FALSE, boards[currentMove]);
16558     }
16559   }
16560 }
16561
16562 static char cseq[12] = "\\   ";
16563
16564 Boolean set_cont_sequence(char *new_seq)
16565 {
16566     int len;
16567     Boolean ret;
16568
16569     // handle bad attempts to set the sequence
16570         if (!new_seq)
16571                 return 0; // acceptable error - no debug
16572
16573     len = strlen(new_seq);
16574     ret = (len > 0) && (len < sizeof(cseq));
16575     if (ret)
16576       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16577     else if (appData.debugMode)
16578       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16579     return ret;
16580 }
16581
16582 /*
16583     reformat a source message so words don't cross the width boundary.  internal
16584     newlines are not removed.  returns the wrapped size (no null character unless
16585     included in source message).  If dest is NULL, only calculate the size required
16586     for the dest buffer.  lp argument indicats line position upon entry, and it's
16587     passed back upon exit.
16588 */
16589 int wrap(char *dest, char *src, int count, int width, int *lp)
16590 {
16591     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16592
16593     cseq_len = strlen(cseq);
16594     old_line = line = *lp;
16595     ansi = len = clen = 0;
16596
16597     for (i=0; i < count; i++)
16598     {
16599         if (src[i] == '\033')
16600             ansi = 1;
16601
16602         // if we hit the width, back up
16603         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16604         {
16605             // store i & len in case the word is too long
16606             old_i = i, old_len = len;
16607
16608             // find the end of the last word
16609             while (i && src[i] != ' ' && src[i] != '\n')
16610             {
16611                 i--;
16612                 len--;
16613             }
16614
16615             // word too long?  restore i & len before splitting it
16616             if ((old_i-i+clen) >= width)
16617             {
16618                 i = old_i;
16619                 len = old_len;
16620             }
16621
16622             // extra space?
16623             if (i && src[i-1] == ' ')
16624                 len--;
16625
16626             if (src[i] != ' ' && src[i] != '\n')
16627             {
16628                 i--;
16629                 if (len)
16630                     len--;
16631             }
16632
16633             // now append the newline and continuation sequence
16634             if (dest)
16635                 dest[len] = '\n';
16636             len++;
16637             if (dest)
16638                 strncpy(dest+len, cseq, cseq_len);
16639             len += cseq_len;
16640             line = cseq_len;
16641             clen = cseq_len;
16642             continue;
16643         }
16644
16645         if (dest)
16646             dest[len] = src[i];
16647         len++;
16648         if (!ansi)
16649             line++;
16650         if (src[i] == '\n')
16651             line = 0;
16652         if (src[i] == 'm')
16653             ansi = 0;
16654     }
16655     if (dest && appData.debugMode)
16656     {
16657         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16658             count, width, line, len, *lp);
16659         show_bytes(debugFP, src, count);
16660         fprintf(debugFP, "\ndest: ");
16661         show_bytes(debugFP, dest, len);
16662         fprintf(debugFP, "\n");
16663     }
16664     *lp = dest ? line : old_line;
16665
16666     return len;
16667 }
16668
16669 // [HGM] vari: routines for shelving variations
16670 Boolean modeRestore = FALSE;
16671
16672 void
16673 PushInner(int firstMove, int lastMove)
16674 {
16675         int i, j, nrMoves = lastMove - firstMove;
16676
16677         // push current tail of game on stack
16678         savedResult[storedGames] = gameInfo.result;
16679         savedDetails[storedGames] = gameInfo.resultDetails;
16680         gameInfo.resultDetails = NULL;
16681         savedFirst[storedGames] = firstMove;
16682         savedLast [storedGames] = lastMove;
16683         savedFramePtr[storedGames] = framePtr;
16684         framePtr -= nrMoves; // reserve space for the boards
16685         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16686             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16687             for(j=0; j<MOVE_LEN; j++)
16688                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16689             for(j=0; j<2*MOVE_LEN; j++)
16690                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16691             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16692             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16693             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16694             pvInfoList[firstMove+i-1].depth = 0;
16695             commentList[framePtr+i] = commentList[firstMove+i];
16696             commentList[firstMove+i] = NULL;
16697         }
16698
16699         storedGames++;
16700         forwardMostMove = firstMove; // truncate game so we can start variation
16701 }
16702
16703 void
16704 PushTail(int firstMove, int lastMove)
16705 {
16706         if(appData.icsActive) { // only in local mode
16707                 forwardMostMove = currentMove; // mimic old ICS behavior
16708                 return;
16709         }
16710         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16711
16712         PushInner(firstMove, lastMove);
16713         if(storedGames == 1) GreyRevert(FALSE);
16714         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16715 }
16716
16717 void
16718 PopInner(Boolean annotate)
16719 {
16720         int i, j, nrMoves;
16721         char buf[8000], moveBuf[20];
16722
16723         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16724         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16725         nrMoves = savedLast[storedGames] - currentMove;
16726         if(annotate) {
16727                 int cnt = 10;
16728                 if(!WhiteOnMove(currentMove))
16729                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16730                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16731                 for(i=currentMove; i<forwardMostMove; i++) {
16732                         if(WhiteOnMove(i))
16733                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16734                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16735                         strcat(buf, moveBuf);
16736                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16737                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16738                 }
16739                 strcat(buf, ")");
16740         }
16741         for(i=1; i<=nrMoves; i++) { // copy last variation back
16742             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16743             for(j=0; j<MOVE_LEN; j++)
16744                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16745             for(j=0; j<2*MOVE_LEN; j++)
16746                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16747             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16748             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16749             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16750             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16751             commentList[currentMove+i] = commentList[framePtr+i];
16752             commentList[framePtr+i] = NULL;
16753         }
16754         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16755         framePtr = savedFramePtr[storedGames];
16756         gameInfo.result = savedResult[storedGames];
16757         if(gameInfo.resultDetails != NULL) {
16758             free(gameInfo.resultDetails);
16759       }
16760         gameInfo.resultDetails = savedDetails[storedGames];
16761         forwardMostMove = currentMove + nrMoves;
16762 }
16763
16764 Boolean
16765 PopTail(Boolean annotate)
16766 {
16767         if(appData.icsActive) return FALSE; // only in local mode
16768         if(!storedGames) return FALSE; // sanity
16769         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16770
16771         PopInner(annotate);
16772         if(currentMove < forwardMostMove) ForwardEvent(); else
16773         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16774
16775         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16776         return TRUE;
16777 }
16778
16779 void
16780 CleanupTail()
16781 {       // remove all shelved variations
16782         int i;
16783         for(i=0; i<storedGames; i++) {
16784             if(savedDetails[i])
16785                 free(savedDetails[i]);
16786             savedDetails[i] = NULL;
16787         }
16788         for(i=framePtr; i<MAX_MOVES; i++) {
16789                 if(commentList[i]) free(commentList[i]);
16790                 commentList[i] = NULL;
16791         }
16792         framePtr = MAX_MOVES-1;
16793         storedGames = 0;
16794 }
16795
16796 void
16797 LoadVariation(int index, char *text)
16798 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16799         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16800         int level = 0, move;
16801
16802         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16803         // first find outermost bracketing variation
16804         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16805             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16806                 if(*p == '{') wait = '}'; else
16807                 if(*p == '[') wait = ']'; else
16808                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16809                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16810             }
16811             if(*p == wait) wait = NULLCHAR; // closing ]} found
16812             p++;
16813         }
16814         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16815         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16816         end[1] = NULLCHAR; // clip off comment beyond variation
16817         ToNrEvent(currentMove-1);
16818         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16819         // kludge: use ParsePV() to append variation to game
16820         move = currentMove;
16821         ParsePV(start, TRUE, TRUE);
16822         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16823         ClearPremoveHighlights();
16824         CommentPopDown();
16825         ToNrEvent(currentMove+1);
16826 }
16827