7b5d2fc0a6d38dce474276a96d949e166d764cc3
[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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy( char *dst, const char *src, size_t count )
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble(u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags(index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
527 };
528
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
534 };
535
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
541 };
542
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackMan, BlackFerz,
547         BlackKing, BlackMan, BlackKnight, BlackRook }
548 };
549
550
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
557 };
558
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
564 };
565
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
571 };
572
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
578 };
579
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
585 };
586
587 #ifdef GOTHIC
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
593 };
594 #else // !GOTHIC
595 #define GothicArray CapablancaArray
596 #endif // !GOTHIC
597
598 #ifdef FALCON
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !FALCON
606 #define FalconArray CapablancaArray
607 #endif // !FALCON
608
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
615
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
622 };
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
626
627
628 Board initialPosition;
629
630
631 /* Convert str to a rating. Checks for special cases of "----",
632
633    "++++", etc. Also strips ()'s */
634 int
635 string_to_rating(str)
636   char *str;
637 {
638   while(*str && !isdigit(*str)) ++str;
639   if (!*str)
640     return 0;   /* One of the special "no rating" cases */
641   else
642     return atoi(str);
643 }
644
645 void
646 ClearProgramStats()
647 {
648     /* Init programStats */
649     programStats.movelist[0] = 0;
650     programStats.depth = 0;
651     programStats.nr_moves = 0;
652     programStats.moves_left = 0;
653     programStats.nodes = 0;
654     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
655     programStats.score = 0;
656     programStats.got_only_move = 0;
657     programStats.got_fail = 0;
658     programStats.line_is_book = 0;
659 }
660
661 void
662 CommonEngineInit()
663 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664     if (appData.firstPlaysBlack) {
665         first.twoMachinesColor = "black\n";
666         second.twoMachinesColor = "white\n";
667     } else {
668         first.twoMachinesColor = "white\n";
669         second.twoMachinesColor = "black\n";
670     }
671
672     first.other = &second;
673     second.other = &first;
674
675     { float norm = 1;
676         if(appData.timeOddsMode) {
677             norm = appData.timeOdds[0];
678             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
679         }
680         first.timeOdds  = appData.timeOdds[0]/norm;
681         second.timeOdds = appData.timeOdds[1]/norm;
682     }
683
684     if(programVersion) free(programVersion);
685     if (appData.noChessProgram) {
686         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687         sprintf(programVersion, "%s", PACKAGE_STRING);
688     } else {
689       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
692     }
693 }
694
695 void
696 UnloadEngine(ChessProgramState *cps)
697 {
698         /* Kill off first chess program */
699         if (cps->isr != NULL)
700           RemoveInputSource(cps->isr);
701         cps->isr = NULL;
702
703         if (cps->pr != NoProc) {
704             ExitAnalyzeMode();
705             DoSleep( appData.delayBeforeQuit );
706             SendToProgram("quit\n", cps);
707             DoSleep( appData.delayAfterQuit );
708             DestroyChildProcess(cps->pr, cps->useSigterm);
709         }
710         cps->pr = NoProc;
711 }
712
713 void
714 ClearOptions(ChessProgramState *cps)
715 {
716     int i;
717     cps->nrOptions = cps->comboCnt = 0;
718     for(i=0; i<MAX_OPTIONS; i++) {
719         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
720         cps->option[i].textValue = 0;
721     }
722 }
723
724 char *engineNames[] = {
725 "first",
726 "second"
727 };
728
729 InitEngine(ChessProgramState *cps, int n)
730 {   // [HGM] all engine initialiation put in a function that does one engine
731
732     ClearOptions(cps);
733
734     cps->which = engineNames[n];
735     cps->maybeThinking = FALSE;
736     cps->pr = NoProc;
737     cps->isr = NULL;
738     cps->sendTime = 2;
739     cps->sendDrawOffers = 1;
740
741     cps->program = appData.chessProgram[n];
742     cps->host = appData.host[n];
743     cps->dir = appData.directory[n];
744     cps->initString = appData.engInitString[n];
745     cps->computerString = appData.computerString[n];
746     cps->useSigint  = TRUE;
747     cps->useSigterm = TRUE;
748     cps->reuse = appData.reuse[n];
749     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
750     cps->useSetboard = FALSE;
751     cps->useSAN = FALSE;
752     cps->usePing = FALSE;
753     cps->lastPing = 0;
754     cps->lastPong = 0;
755     cps->usePlayother = FALSE;
756     cps->useColors = TRUE;
757     cps->useUsermove = FALSE;
758     cps->sendICS = FALSE;
759     cps->sendName = appData.icsActive;
760     cps->sdKludge = FALSE;
761     cps->stKludge = FALSE;
762     TidyProgramName(cps->program, cps->host, cps->tidy);
763     cps->matchWins = 0;
764     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
765     cps->analysisSupport = 2; /* detect */
766     cps->analyzing = FALSE;
767     cps->initDone = FALSE;
768
769     /* New features added by Tord: */
770     cps->useFEN960 = FALSE;
771     cps->useOOCastle = TRUE;
772     /* End of new features added by Tord. */
773     cps->fenOverride  = appData.fenOverride[n];
774
775     /* [HGM] time odds: set factor for each machine */
776     cps->timeOdds  = appData.timeOdds[n];
777
778     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
779     cps->accumulateTC = appData.accumulateTC[n];
780     cps->maxNrOfSessions = 1;
781
782     /* [HGM] debug */
783     cps->debug = FALSE;
784     cps->supportsNPS = UNKNOWN;
785
786     /* [HGM] options */
787     cps->optionSettings  = appData.engOptions[n];
788
789     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
790     cps->isUCI = appData.isUCI[n]; /* [AS] */
791     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
792
793     if (appData.protocolVersion[n] > PROTOVER
794         || appData.protocolVersion[n] < 1)
795       {
796         char buf[MSG_SIZ];
797         int len;
798
799         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
800                        appData.protocolVersion[n]);
801         if( (len > MSG_SIZ) && appData.debugMode )
802           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
803
804         DisplayFatalError(buf, 0, 2);
805       }
806     else
807       {
808         cps->protocolVersion = appData.protocolVersion[n];
809       }
810
811     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
812 }
813
814 ChessProgramState *savCps;
815
816 void
817 LoadEngine()
818 {
819     int i;
820     if(WaitForEngine(savCps, LoadEngine)) return;
821     CommonEngineInit(); // recalculate time odds
822     if(gameInfo.variant != StringToVariant(appData.variant)) {
823         // we changed variant when loading the engine; this forces us to reset
824         Reset(TRUE, savCps != &first);
825         EditGameEvent(); // for consistency with other path, as Reset changes mode
826     }
827     InitChessProgram(savCps, FALSE);
828     SendToProgram("force\n", savCps);
829     DisplayMessage("", "");
830     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
831     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
832     ThawUI();
833     SetGNUMode();
834 }
835
836 void
837 ReplaceEngine(ChessProgramState *cps, int n)
838 {
839     EditGameEvent();
840     UnloadEngine(cps);
841     appData.noChessProgram = False;
842     appData.clockMode = True;
843     InitEngine(cps, n);
844     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
845     LoadEngine();
846 }
847
848 void
849 InitBackEnd1()
850 {
851     int matched, min, sec;
852
853     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
854     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
855
856     GetTimeMark(&programStartTime);
857     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
858
859     ClearProgramStats();
860     programStats.ok_to_send = 1;
861     programStats.seen_stat = 0;
862
863     /*
864      * Initialize game list
865      */
866     ListNew(&gameList);
867
868
869     /*
870      * Internet chess server status
871      */
872     if (appData.icsActive) {
873         appData.matchMode = FALSE;
874         appData.matchGames = 0;
875 #if ZIPPY
876         appData.noChessProgram = !appData.zippyPlay;
877 #else
878         appData.zippyPlay = FALSE;
879         appData.zippyTalk = FALSE;
880         appData.noChessProgram = TRUE;
881 #endif
882         if (*appData.icsHelper != NULLCHAR) {
883             appData.useTelnet = TRUE;
884             appData.telnetProgram = appData.icsHelper;
885         }
886     } else {
887         appData.zippyTalk = appData.zippyPlay = FALSE;
888     }
889
890     /* [AS] Initialize pv info list [HGM] and game state */
891     {
892         int i, j;
893
894         for( i=0; i<=framePtr; i++ ) {
895             pvInfoList[i].depth = -1;
896             boards[i][EP_STATUS] = EP_NONE;
897             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
898         }
899     }
900
901     /*
902      * Parse timeControl resource
903      */
904     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
905                           appData.movesPerSession)) {
906         char buf[MSG_SIZ];
907         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
908         DisplayFatalError(buf, 0, 2);
909     }
910
911     /*
912      * Parse searchTime resource
913      */
914     if (*appData.searchTime != NULLCHAR) {
915         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
916         if (matched == 1) {
917             searchTime = min * 60;
918         } else if (matched == 2) {
919             searchTime = min * 60 + sec;
920         } else {
921             char buf[MSG_SIZ];
922             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
923             DisplayFatalError(buf, 0, 2);
924         }
925     }
926
927     /* [AS] Adjudication threshold */
928     adjudicateLossThreshold = appData.adjudicateLossThreshold;
929
930     InitEngine(&first, 0);
931     InitEngine(&second, 1);
932     CommonEngineInit();
933
934     if (appData.icsActive) {
935         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
936     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
937         appData.clockMode = FALSE;
938         first.sendTime = second.sendTime = 0;
939     }
940
941 #if ZIPPY
942     /* Override some settings from environment variables, for backward
943        compatibility.  Unfortunately it's not feasible to have the env
944        vars just set defaults, at least in xboard.  Ugh.
945     */
946     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
947       ZippyInit();
948     }
949 #endif
950
951     if (!appData.icsActive) {
952       char buf[MSG_SIZ];
953       int len;
954
955       /* Check for variants that are supported only in ICS mode,
956          or not at all.  Some that are accepted here nevertheless
957          have bugs; see comments below.
958       */
959       VariantClass variant = StringToVariant(appData.variant);
960       switch (variant) {
961       case VariantBughouse:     /* need four players and two boards */
962       case VariantKriegspiel:   /* need to hide pieces and move details */
963         /* case VariantFischeRandom: (Fabien: moved below) */
964         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
965         if( (len > MSG_SIZ) && appData.debugMode )
966           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
967
968         DisplayFatalError(buf, 0, 2);
969         return;
970
971       case VariantUnknown:
972       case VariantLoadable:
973       case Variant29:
974       case Variant30:
975       case Variant31:
976       case Variant32:
977       case Variant33:
978       case Variant34:
979       case Variant35:
980       case Variant36:
981       default:
982         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
983         if( (len > MSG_SIZ) && appData.debugMode )
984           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
985
986         DisplayFatalError(buf, 0, 2);
987         return;
988
989       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
990       case VariantFairy:      /* [HGM] TestLegality definitely off! */
991       case VariantGothic:     /* [HGM] should work */
992       case VariantCapablanca: /* [HGM] should work */
993       case VariantCourier:    /* [HGM] initial forced moves not implemented */
994       case VariantShogi:      /* [HGM] could still mate with pawn drop */
995       case VariantKnightmate: /* [HGM] should work */
996       case VariantCylinder:   /* [HGM] untested */
997       case VariantFalcon:     /* [HGM] untested */
998       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
999                                  offboard interposition not understood */
1000       case VariantNormal:     /* definitely works! */
1001       case VariantWildCastle: /* pieces not automatically shuffled */
1002       case VariantNoCastle:   /* pieces not automatically shuffled */
1003       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1004       case VariantLosers:     /* should work except for win condition,
1005                                  and doesn't know captures are mandatory */
1006       case VariantSuicide:    /* should work except for win condition,
1007                                  and doesn't know captures are mandatory */
1008       case VariantGiveaway:   /* should work except for win condition,
1009                                  and doesn't know captures are mandatory */
1010       case VariantTwoKings:   /* should work */
1011       case VariantAtomic:     /* should work except for win condition */
1012       case Variant3Check:     /* should work except for win condition */
1013       case VariantShatranj:   /* should work except for all win conditions */
1014       case VariantMakruk:     /* should work except for daw countdown */
1015       case VariantBerolina:   /* might work if TestLegality is off */
1016       case VariantCapaRandom: /* should work */
1017       case VariantJanus:      /* should work */
1018       case VariantSuper:      /* experimental */
1019       case VariantGreat:      /* experimental, requires legality testing to be off */
1020       case VariantSChess:     /* S-Chess, should work */
1021       case VariantSpartan:    /* should work */
1022         break;
1023       }
1024     }
1025
1026 }
1027
1028 int NextIntegerFromString( char ** str, long * value )
1029 {
1030     int result = -1;
1031     char * s = *str;
1032
1033     while( *s == ' ' || *s == '\t' ) {
1034         s++;
1035     }
1036
1037     *value = 0;
1038
1039     if( *s >= '0' && *s <= '9' ) {
1040         while( *s >= '0' && *s <= '9' ) {
1041             *value = *value * 10 + (*s - '0');
1042             s++;
1043         }
1044
1045         result = 0;
1046     }
1047
1048     *str = s;
1049
1050     return result;
1051 }
1052
1053 int NextTimeControlFromString( char ** str, long * value )
1054 {
1055     long temp;
1056     int result = NextIntegerFromString( str, &temp );
1057
1058     if( result == 0 ) {
1059         *value = temp * 60; /* Minutes */
1060         if( **str == ':' ) {
1061             (*str)++;
1062             result = NextIntegerFromString( str, &temp );
1063             *value += temp; /* Seconds */
1064         }
1065     }
1066
1067     return result;
1068 }
1069
1070 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1071 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1072     int result = -1, type = 0; long temp, temp2;
1073
1074     if(**str != ':') return -1; // old params remain in force!
1075     (*str)++;
1076     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1077     if( NextIntegerFromString( str, &temp ) ) return -1;
1078     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1079
1080     if(**str != '/') {
1081         /* time only: incremental or sudden-death time control */
1082         if(**str == '+') { /* increment follows; read it */
1083             (*str)++;
1084             if(**str == '!') type = *(*str)++; // Bronstein TC
1085             if(result = NextIntegerFromString( str, &temp2)) return -1;
1086             *inc = temp2 * 1000;
1087             if(**str == '.') { // read fraction of increment
1088                 char *start = ++(*str);
1089                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1090                 temp2 *= 1000;
1091                 while(start++ < *str) temp2 /= 10;
1092                 *inc += temp2;
1093             }
1094         } else *inc = 0;
1095         *moves = 0; *tc = temp * 1000; *incType = type;
1096         return 0;
1097     }
1098
1099     (*str)++; /* classical time control */
1100     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1101
1102     if(result == 0) {
1103         *moves = temp;
1104         *tc    = temp2 * 1000;
1105         *inc   = 0;
1106         *incType = type;
1107     }
1108     return result;
1109 }
1110
1111 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1112 {   /* [HGM] get time to add from the multi-session time-control string */
1113     int incType, moves=1; /* kludge to force reading of first session */
1114     long time, increment;
1115     char *s = tcString;
1116
1117     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1118     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1119     do {
1120         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1121         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1122         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1123         if(movenr == -1) return time;    /* last move before new session     */
1124         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1125         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1126         if(!moves) return increment;     /* current session is incremental   */
1127         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1128     } while(movenr >= -1);               /* try again for next session       */
1129
1130     return 0; // no new time quota on this move
1131 }
1132
1133 int
1134 ParseTimeControl(tc, ti, mps)
1135      char *tc;
1136      float ti;
1137      int mps;
1138 {
1139   long tc1;
1140   long tc2;
1141   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1142   int min, sec=0;
1143
1144   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1145   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1146       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1147   if(ti > 0) {
1148
1149     if(mps)
1150       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1151     else 
1152       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1153   } else {
1154     if(mps)
1155       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1156     else 
1157       snprintf(buf, MSG_SIZ, ":%s", mytc);
1158   }
1159   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1160   
1161   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1162     return FALSE;
1163   }
1164
1165   if( *tc == '/' ) {
1166     /* Parse second time control */
1167     tc++;
1168
1169     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1170       return FALSE;
1171     }
1172
1173     if( tc2 == 0 ) {
1174       return FALSE;
1175     }
1176
1177     timeControl_2 = tc2 * 1000;
1178   }
1179   else {
1180     timeControl_2 = 0;
1181   }
1182
1183   if( tc1 == 0 ) {
1184     return FALSE;
1185   }
1186
1187   timeControl = tc1 * 1000;
1188
1189   if (ti >= 0) {
1190     timeIncrement = ti * 1000;  /* convert to ms */
1191     movesPerSession = 0;
1192   } else {
1193     timeIncrement = 0;
1194     movesPerSession = mps;
1195   }
1196   return TRUE;
1197 }
1198
1199 void
1200 InitBackEnd2()
1201 {
1202     if (appData.debugMode) {
1203         fprintf(debugFP, "%s\n", programVersion);
1204     }
1205
1206     set_cont_sequence(appData.wrapContSeq);
1207     if (appData.matchGames > 0) {
1208         appData.matchMode = TRUE;
1209     } else if (appData.matchMode) {
1210         appData.matchGames = 1;
1211     }
1212     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1213         appData.matchGames = appData.sameColorGames;
1214     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1215         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1216         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1217     }
1218     Reset(TRUE, FALSE);
1219     if (appData.noChessProgram || first.protocolVersion == 1) {
1220       InitBackEnd3();
1221     } else {
1222       /* kludge: allow timeout for initial "feature" commands */
1223       FreezeUI();
1224       DisplayMessage("", _("Starting chess program"));
1225       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1226     }
1227 }
1228
1229 void
1230 MatchEvent(int mode)
1231 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1232         /* Set up machine vs. machine match */
1233         if (appData.noChessProgram) {
1234             DisplayFatalError(_("Can't have a match with no chess programs"),
1235                               0, 2);
1236             return;
1237         }
1238         matchMode = mode;
1239         matchGame = 1;
1240         if (*appData.loadGameFile != NULLCHAR) {
1241             int index = appData.loadGameIndex; // [HGM] autoinc
1242             if(index<0) lastIndex = index = 1;
1243             if (!LoadGameFromFile(appData.loadGameFile,
1244                                   index,
1245                                   appData.loadGameFile, FALSE)) {
1246                 DisplayFatalError(_("Bad game file"), 0, 1);
1247                 return;
1248             }
1249         } else if (*appData.loadPositionFile != NULLCHAR) {
1250             int index = appData.loadPositionIndex; // [HGM] autoinc
1251             if(index<0) lastIndex = index = 1;
1252             if (!LoadPositionFromFile(appData.loadPositionFile,
1253                                       index,
1254                                       appData.loadPositionFile)) {
1255                 DisplayFatalError(_("Bad position file"), 0, 1);
1256                 return;
1257             }
1258         }
1259         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1260         TwoMachinesEvent();
1261 }
1262
1263 void
1264 InitBackEnd3 P((void))
1265 {
1266     GameMode initialMode;
1267     char buf[MSG_SIZ];
1268     int err, len;
1269
1270     InitChessProgram(&first, startedFromSetupPosition);
1271
1272     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1273         free(programVersion);
1274         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1275         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1276     }
1277
1278     if (appData.icsActive) {
1279 #ifdef WIN32
1280         /* [DM] Make a console window if needed [HGM] merged ifs */
1281         ConsoleCreate();
1282 #endif
1283         err = establish();
1284         if (err != 0)
1285           {
1286             if (*appData.icsCommPort != NULLCHAR)
1287               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1288                              appData.icsCommPort);
1289             else
1290               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1291                         appData.icsHost, appData.icsPort);
1292
1293             if( (len > MSG_SIZ) && appData.debugMode )
1294               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1295
1296             DisplayFatalError(buf, err, 1);
1297             return;
1298         }
1299         SetICSMode();
1300         telnetISR =
1301           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1302         fromUserISR =
1303           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1304         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1305             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1306     } else if (appData.noChessProgram) {
1307         SetNCPMode();
1308     } else {
1309         SetGNUMode();
1310     }
1311
1312     if (*appData.cmailGameName != NULLCHAR) {
1313         SetCmailMode();
1314         OpenLoopback(&cmailPR);
1315         cmailISR =
1316           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1317     }
1318
1319     ThawUI();
1320     DisplayMessage("", "");
1321     if (StrCaseCmp(appData.initialMode, "") == 0) {
1322       initialMode = BeginningOfGame;
1323     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1324       initialMode = TwoMachinesPlay;
1325     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1326       initialMode = AnalyzeFile;
1327     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1328       initialMode = AnalyzeMode;
1329     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1330       initialMode = MachinePlaysWhite;
1331     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1332       initialMode = MachinePlaysBlack;
1333     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1334       initialMode = EditGame;
1335     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1336       initialMode = EditPosition;
1337     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1338       initialMode = Training;
1339     } else {
1340       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1341       if( (len > MSG_SIZ) && appData.debugMode )
1342         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1343
1344       DisplayFatalError(buf, 0, 2);
1345       return;
1346     }
1347
1348     if (appData.matchMode) {
1349         MatchEvent(TRUE);
1350     } else if (*appData.cmailGameName != NULLCHAR) {
1351         /* Set up cmail mode */
1352         ReloadCmailMsgEvent(TRUE);
1353     } else {
1354         /* Set up other modes */
1355         if (initialMode == AnalyzeFile) {
1356           if (*appData.loadGameFile == NULLCHAR) {
1357             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1358             return;
1359           }
1360         }
1361         if (*appData.loadGameFile != NULLCHAR) {
1362             (void) LoadGameFromFile(appData.loadGameFile,
1363                                     appData.loadGameIndex,
1364                                     appData.loadGameFile, TRUE);
1365         } else if (*appData.loadPositionFile != NULLCHAR) {
1366             (void) LoadPositionFromFile(appData.loadPositionFile,
1367                                         appData.loadPositionIndex,
1368                                         appData.loadPositionFile);
1369             /* [HGM] try to make self-starting even after FEN load */
1370             /* to allow automatic setup of fairy variants with wtm */
1371             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1372                 gameMode = BeginningOfGame;
1373                 setboardSpoiledMachineBlack = 1;
1374             }
1375             /* [HGM] loadPos: make that every new game uses the setup */
1376             /* from file as long as we do not switch variant          */
1377             if(!blackPlaysFirst) {
1378                 startedFromPositionFile = TRUE;
1379                 CopyBoard(filePosition, boards[0]);
1380             }
1381         }
1382         if (initialMode == AnalyzeMode) {
1383           if (appData.noChessProgram) {
1384             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1385             return;
1386           }
1387           if (appData.icsActive) {
1388             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1389             return;
1390           }
1391           AnalyzeModeEvent();
1392         } else if (initialMode == AnalyzeFile) {
1393           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1394           ShowThinkingEvent();
1395           AnalyzeFileEvent();
1396           AnalysisPeriodicEvent(1);
1397         } else if (initialMode == MachinePlaysWhite) {
1398           if (appData.noChessProgram) {
1399             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1400                               0, 2);
1401             return;
1402           }
1403           if (appData.icsActive) {
1404             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1405                               0, 2);
1406             return;
1407           }
1408           MachineWhiteEvent();
1409         } else if (initialMode == MachinePlaysBlack) {
1410           if (appData.noChessProgram) {
1411             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1412                               0, 2);
1413             return;
1414           }
1415           if (appData.icsActive) {
1416             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1417                               0, 2);
1418             return;
1419           }
1420           MachineBlackEvent();
1421         } else if (initialMode == TwoMachinesPlay) {
1422           if (appData.noChessProgram) {
1423             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1424                               0, 2);
1425             return;
1426           }
1427           if (appData.icsActive) {
1428             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1429                               0, 2);
1430             return;
1431           }
1432           TwoMachinesEvent();
1433         } else if (initialMode == EditGame) {
1434           EditGameEvent();
1435         } else if (initialMode == EditPosition) {
1436           EditPositionEvent();
1437         } else if (initialMode == Training) {
1438           if (*appData.loadGameFile == NULLCHAR) {
1439             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1440             return;
1441           }
1442           TrainingEvent();
1443         }
1444     }
1445 }
1446
1447 /*
1448  * Establish will establish a contact to a remote host.port.
1449  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1450  *  used to talk to the host.
1451  * Returns 0 if okay, error code if not.
1452  */
1453 int
1454 establish()
1455 {
1456     char buf[MSG_SIZ];
1457
1458     if (*appData.icsCommPort != NULLCHAR) {
1459         /* Talk to the host through a serial comm port */
1460         return OpenCommPort(appData.icsCommPort, &icsPR);
1461
1462     } else if (*appData.gateway != NULLCHAR) {
1463         if (*appData.remoteShell == NULLCHAR) {
1464             /* Use the rcmd protocol to run telnet program on a gateway host */
1465             snprintf(buf, sizeof(buf), "%s %s %s",
1466                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1467             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1468
1469         } else {
1470             /* Use the rsh program to run telnet program on a gateway host */
1471             if (*appData.remoteUser == NULLCHAR) {
1472                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1473                         appData.gateway, appData.telnetProgram,
1474                         appData.icsHost, appData.icsPort);
1475             } else {
1476                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1477                         appData.remoteShell, appData.gateway,
1478                         appData.remoteUser, appData.telnetProgram,
1479                         appData.icsHost, appData.icsPort);
1480             }
1481             return StartChildProcess(buf, "", &icsPR);
1482
1483         }
1484     } else if (appData.useTelnet) {
1485         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1486
1487     } else {
1488         /* TCP socket interface differs somewhat between
1489            Unix and NT; handle details in the front end.
1490            */
1491         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1492     }
1493 }
1494
1495 void EscapeExpand(char *p, char *q)
1496 {       // [HGM] initstring: routine to shape up string arguments
1497         while(*p++ = *q++) if(p[-1] == '\\')
1498             switch(*q++) {
1499                 case 'n': p[-1] = '\n'; break;
1500                 case 'r': p[-1] = '\r'; break;
1501                 case 't': p[-1] = '\t'; break;
1502                 case '\\': p[-1] = '\\'; break;
1503                 case 0: *p = 0; return;
1504                 default: p[-1] = q[-1]; break;
1505             }
1506 }
1507
1508 void
1509 show_bytes(fp, buf, count)
1510      FILE *fp;
1511      char *buf;
1512      int count;
1513 {
1514     while (count--) {
1515         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1516             fprintf(fp, "\\%03o", *buf & 0xff);
1517         } else {
1518             putc(*buf, fp);
1519         }
1520         buf++;
1521     }
1522     fflush(fp);
1523 }
1524
1525 /* Returns an errno value */
1526 int
1527 OutputMaybeTelnet(pr, message, count, outError)
1528      ProcRef pr;
1529      char *message;
1530      int count;
1531      int *outError;
1532 {
1533     char buf[8192], *p, *q, *buflim;
1534     int left, newcount, outcount;
1535
1536     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1537         *appData.gateway != NULLCHAR) {
1538         if (appData.debugMode) {
1539             fprintf(debugFP, ">ICS: ");
1540             show_bytes(debugFP, message, count);
1541             fprintf(debugFP, "\n");
1542         }
1543         return OutputToProcess(pr, message, count, outError);
1544     }
1545
1546     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1547     p = message;
1548     q = buf;
1549     left = count;
1550     newcount = 0;
1551     while (left) {
1552         if (q >= buflim) {
1553             if (appData.debugMode) {
1554                 fprintf(debugFP, ">ICS: ");
1555                 show_bytes(debugFP, buf, newcount);
1556                 fprintf(debugFP, "\n");
1557             }
1558             outcount = OutputToProcess(pr, buf, newcount, outError);
1559             if (outcount < newcount) return -1; /* to be sure */
1560             q = buf;
1561             newcount = 0;
1562         }
1563         if (*p == '\n') {
1564             *q++ = '\r';
1565             newcount++;
1566         } else if (((unsigned char) *p) == TN_IAC) {
1567             *q++ = (char) TN_IAC;
1568             newcount ++;
1569         }
1570         *q++ = *p++;
1571         newcount++;
1572         left--;
1573     }
1574     if (appData.debugMode) {
1575         fprintf(debugFP, ">ICS: ");
1576         show_bytes(debugFP, buf, newcount);
1577         fprintf(debugFP, "\n");
1578     }
1579     outcount = OutputToProcess(pr, buf, newcount, outError);
1580     if (outcount < newcount) return -1; /* to be sure */
1581     return count;
1582 }
1583
1584 void
1585 read_from_player(isr, closure, message, count, error)
1586      InputSourceRef isr;
1587      VOIDSTAR closure;
1588      char *message;
1589      int count;
1590      int error;
1591 {
1592     int outError, outCount;
1593     static int gotEof = 0;
1594
1595     /* Pass data read from player on to ICS */
1596     if (count > 0) {
1597         gotEof = 0;
1598         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1599         if (outCount < count) {
1600             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1601         }
1602     } else if (count < 0) {
1603         RemoveInputSource(isr);
1604         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1605     } else if (gotEof++ > 0) {
1606         RemoveInputSource(isr);
1607         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1608     }
1609 }
1610
1611 void
1612 KeepAlive()
1613 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1614     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1615     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1616     SendToICS("date\n");
1617     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1618 }
1619
1620 /* added routine for printf style output to ics */
1621 void ics_printf(char *format, ...)
1622 {
1623     char buffer[MSG_SIZ];
1624     va_list args;
1625
1626     va_start(args, format);
1627     vsnprintf(buffer, sizeof(buffer), format, args);
1628     buffer[sizeof(buffer)-1] = '\0';
1629     SendToICS(buffer);
1630     va_end(args);
1631 }
1632
1633 void
1634 SendToICS(s)
1635      char *s;
1636 {
1637     int count, outCount, outError;
1638
1639     if (icsPR == NULL) return;
1640
1641     count = strlen(s);
1642     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1643     if (outCount < count) {
1644         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1645     }
1646 }
1647
1648 /* This is used for sending logon scripts to the ICS. Sending
1649    without a delay causes problems when using timestamp on ICC
1650    (at least on my machine). */
1651 void
1652 SendToICSDelayed(s,msdelay)
1653      char *s;
1654      long msdelay;
1655 {
1656     int count, outCount, outError;
1657
1658     if (icsPR == NULL) return;
1659
1660     count = strlen(s);
1661     if (appData.debugMode) {
1662         fprintf(debugFP, ">ICS: ");
1663         show_bytes(debugFP, s, count);
1664         fprintf(debugFP, "\n");
1665     }
1666     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1667                                       msdelay);
1668     if (outCount < count) {
1669         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1670     }
1671 }
1672
1673
1674 /* Remove all highlighting escape sequences in s
1675    Also deletes any suffix starting with '('
1676    */
1677 char *
1678 StripHighlightAndTitle(s)
1679      char *s;
1680 {
1681     static char retbuf[MSG_SIZ];
1682     char *p = retbuf;
1683
1684     while (*s != NULLCHAR) {
1685         while (*s == '\033') {
1686             while (*s != NULLCHAR && !isalpha(*s)) s++;
1687             if (*s != NULLCHAR) s++;
1688         }
1689         while (*s != NULLCHAR && *s != '\033') {
1690             if (*s == '(' || *s == '[') {
1691                 *p = NULLCHAR;
1692                 return retbuf;
1693             }
1694             *p++ = *s++;
1695         }
1696     }
1697     *p = NULLCHAR;
1698     return retbuf;
1699 }
1700
1701 /* Remove all highlighting escape sequences in s */
1702 char *
1703 StripHighlight(s)
1704      char *s;
1705 {
1706     static char retbuf[MSG_SIZ];
1707     char *p = retbuf;
1708
1709     while (*s != NULLCHAR) {
1710         while (*s == '\033') {
1711             while (*s != NULLCHAR && !isalpha(*s)) s++;
1712             if (*s != NULLCHAR) s++;
1713         }
1714         while (*s != NULLCHAR && *s != '\033') {
1715             *p++ = *s++;
1716         }
1717     }
1718     *p = NULLCHAR;
1719     return retbuf;
1720 }
1721
1722 char *variantNames[] = VARIANT_NAMES;
1723 char *
1724 VariantName(v)
1725      VariantClass v;
1726 {
1727     return variantNames[v];
1728 }
1729
1730
1731 /* Identify a variant from the strings the chess servers use or the
1732    PGN Variant tag names we use. */
1733 VariantClass
1734 StringToVariant(e)
1735      char *e;
1736 {
1737     char *p;
1738     int wnum = -1;
1739     VariantClass v = VariantNormal;
1740     int i, found = FALSE;
1741     char buf[MSG_SIZ];
1742     int len;
1743
1744     if (!e) return v;
1745
1746     /* [HGM] skip over optional board-size prefixes */
1747     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1748         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1749         while( *e++ != '_');
1750     }
1751
1752     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1753         v = VariantNormal;
1754         found = TRUE;
1755     } else
1756     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1757       if (StrCaseStr(e, variantNames[i])) {
1758         v = (VariantClass) i;
1759         found = TRUE;
1760         break;
1761       }
1762     }
1763
1764     if (!found) {
1765       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1766           || StrCaseStr(e, "wild/fr")
1767           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1768         v = VariantFischeRandom;
1769       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1770                  (i = 1, p = StrCaseStr(e, "w"))) {
1771         p += i;
1772         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1773         if (isdigit(*p)) {
1774           wnum = atoi(p);
1775         } else {
1776           wnum = -1;
1777         }
1778         switch (wnum) {
1779         case 0: /* FICS only, actually */
1780         case 1:
1781           /* Castling legal even if K starts on d-file */
1782           v = VariantWildCastle;
1783           break;
1784         case 2:
1785         case 3:
1786         case 4:
1787           /* Castling illegal even if K & R happen to start in
1788              normal positions. */
1789           v = VariantNoCastle;
1790           break;
1791         case 5:
1792         case 7:
1793         case 8:
1794         case 10:
1795         case 11:
1796         case 12:
1797         case 13:
1798         case 14:
1799         case 15:
1800         case 18:
1801         case 19:
1802           /* Castling legal iff K & R start in normal positions */
1803           v = VariantNormal;
1804           break;
1805         case 6:
1806         case 20:
1807         case 21:
1808           /* Special wilds for position setup; unclear what to do here */
1809           v = VariantLoadable;
1810           break;
1811         case 9:
1812           /* Bizarre ICC game */
1813           v = VariantTwoKings;
1814           break;
1815         case 16:
1816           v = VariantKriegspiel;
1817           break;
1818         case 17:
1819           v = VariantLosers;
1820           break;
1821         case 22:
1822           v = VariantFischeRandom;
1823           break;
1824         case 23:
1825           v = VariantCrazyhouse;
1826           break;
1827         case 24:
1828           v = VariantBughouse;
1829           break;
1830         case 25:
1831           v = Variant3Check;
1832           break;
1833         case 26:
1834           /* Not quite the same as FICS suicide! */
1835           v = VariantGiveaway;
1836           break;
1837         case 27:
1838           v = VariantAtomic;
1839           break;
1840         case 28:
1841           v = VariantShatranj;
1842           break;
1843
1844         /* Temporary names for future ICC types.  The name *will* change in
1845            the next xboard/WinBoard release after ICC defines it. */
1846         case 29:
1847           v = Variant29;
1848           break;
1849         case 30:
1850           v = Variant30;
1851           break;
1852         case 31:
1853           v = Variant31;
1854           break;
1855         case 32:
1856           v = Variant32;
1857           break;
1858         case 33:
1859           v = Variant33;
1860           break;
1861         case 34:
1862           v = Variant34;
1863           break;
1864         case 35:
1865           v = Variant35;
1866           break;
1867         case 36:
1868           v = Variant36;
1869           break;
1870         case 37:
1871           v = VariantShogi;
1872           break;
1873         case 38:
1874           v = VariantXiangqi;
1875           break;
1876         case 39:
1877           v = VariantCourier;
1878           break;
1879         case 40:
1880           v = VariantGothic;
1881           break;
1882         case 41:
1883           v = VariantCapablanca;
1884           break;
1885         case 42:
1886           v = VariantKnightmate;
1887           break;
1888         case 43:
1889           v = VariantFairy;
1890           break;
1891         case 44:
1892           v = VariantCylinder;
1893           break;
1894         case 45:
1895           v = VariantFalcon;
1896           break;
1897         case 46:
1898           v = VariantCapaRandom;
1899           break;
1900         case 47:
1901           v = VariantBerolina;
1902           break;
1903         case 48:
1904           v = VariantJanus;
1905           break;
1906         case 49:
1907           v = VariantSuper;
1908           break;
1909         case 50:
1910           v = VariantGreat;
1911           break;
1912         case -1:
1913           /* Found "wild" or "w" in the string but no number;
1914              must assume it's normal chess. */
1915           v = VariantNormal;
1916           break;
1917         default:
1918           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1919           if( (len > MSG_SIZ) && appData.debugMode )
1920             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1921
1922           DisplayError(buf, 0);
1923           v = VariantUnknown;
1924           break;
1925         }
1926       }
1927     }
1928     if (appData.debugMode) {
1929       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1930               e, wnum, VariantName(v));
1931     }
1932     return v;
1933 }
1934
1935 static int leftover_start = 0, leftover_len = 0;
1936 char star_match[STAR_MATCH_N][MSG_SIZ];
1937
1938 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1939    advance *index beyond it, and set leftover_start to the new value of
1940    *index; else return FALSE.  If pattern contains the character '*', it
1941    matches any sequence of characters not containing '\r', '\n', or the
1942    character following the '*' (if any), and the matched sequence(s) are
1943    copied into star_match.
1944    */
1945 int
1946 looking_at(buf, index, pattern)
1947      char *buf;
1948      int *index;
1949      char *pattern;
1950 {
1951     char *bufp = &buf[*index], *patternp = pattern;
1952     int star_count = 0;
1953     char *matchp = star_match[0];
1954
1955     for (;;) {
1956         if (*patternp == NULLCHAR) {
1957             *index = leftover_start = bufp - buf;
1958             *matchp = NULLCHAR;
1959             return TRUE;
1960         }
1961         if (*bufp == NULLCHAR) return FALSE;
1962         if (*patternp == '*') {
1963             if (*bufp == *(patternp + 1)) {
1964                 *matchp = NULLCHAR;
1965                 matchp = star_match[++star_count];
1966                 patternp += 2;
1967                 bufp++;
1968                 continue;
1969             } else if (*bufp == '\n' || *bufp == '\r') {
1970                 patternp++;
1971                 if (*patternp == NULLCHAR)
1972                   continue;
1973                 else
1974                   return FALSE;
1975             } else {
1976                 *matchp++ = *bufp++;
1977                 continue;
1978             }
1979         }
1980         if (*patternp != *bufp) return FALSE;
1981         patternp++;
1982         bufp++;
1983     }
1984 }
1985
1986 void
1987 SendToPlayer(data, length)
1988      char *data;
1989      int length;
1990 {
1991     int error, outCount;
1992     outCount = OutputToProcess(NoProc, data, length, &error);
1993     if (outCount < length) {
1994         DisplayFatalError(_("Error writing to display"), error, 1);
1995     }
1996 }
1997
1998 void
1999 PackHolding(packed, holding)
2000      char packed[];
2001      char *holding;
2002 {
2003     char *p = holding;
2004     char *q = packed;
2005     int runlength = 0;
2006     int curr = 9999;
2007     do {
2008         if (*p == curr) {
2009             runlength++;
2010         } else {
2011             switch (runlength) {
2012               case 0:
2013                 break;
2014               case 1:
2015                 *q++ = curr;
2016                 break;
2017               case 2:
2018                 *q++ = curr;
2019                 *q++ = curr;
2020                 break;
2021               default:
2022                 sprintf(q, "%d", runlength);
2023                 while (*q) q++;
2024                 *q++ = curr;
2025                 break;
2026             }
2027             runlength = 1;
2028             curr = *p;
2029         }
2030     } while (*p++);
2031     *q = NULLCHAR;
2032 }
2033
2034 /* Telnet protocol requests from the front end */
2035 void
2036 TelnetRequest(ddww, option)
2037      unsigned char ddww, option;
2038 {
2039     unsigned char msg[3];
2040     int outCount, outError;
2041
2042     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2043
2044     if (appData.debugMode) {
2045         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2046         switch (ddww) {
2047           case TN_DO:
2048             ddwwStr = "DO";
2049             break;
2050           case TN_DONT:
2051             ddwwStr = "DONT";
2052             break;
2053           case TN_WILL:
2054             ddwwStr = "WILL";
2055             break;
2056           case TN_WONT:
2057             ddwwStr = "WONT";
2058             break;
2059           default:
2060             ddwwStr = buf1;
2061             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2062             break;
2063         }
2064         switch (option) {
2065           case TN_ECHO:
2066             optionStr = "ECHO";
2067             break;
2068           default:
2069             optionStr = buf2;
2070             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2071             break;
2072         }
2073         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2074     }
2075     msg[0] = TN_IAC;
2076     msg[1] = ddww;
2077     msg[2] = option;
2078     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2079     if (outCount < 3) {
2080         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2081     }
2082 }
2083
2084 void
2085 DoEcho()
2086 {
2087     if (!appData.icsActive) return;
2088     TelnetRequest(TN_DO, TN_ECHO);
2089 }
2090
2091 void
2092 DontEcho()
2093 {
2094     if (!appData.icsActive) return;
2095     TelnetRequest(TN_DONT, TN_ECHO);
2096 }
2097
2098 void
2099 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2100 {
2101     /* put the holdings sent to us by the server on the board holdings area */
2102     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2103     char p;
2104     ChessSquare piece;
2105
2106     if(gameInfo.holdingsWidth < 2)  return;
2107     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2108         return; // prevent overwriting by pre-board holdings
2109
2110     if( (int)lowestPiece >= BlackPawn ) {
2111         holdingsColumn = 0;
2112         countsColumn = 1;
2113         holdingsStartRow = BOARD_HEIGHT-1;
2114         direction = -1;
2115     } else {
2116         holdingsColumn = BOARD_WIDTH-1;
2117         countsColumn = BOARD_WIDTH-2;
2118         holdingsStartRow = 0;
2119         direction = 1;
2120     }
2121
2122     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2123         board[i][holdingsColumn] = EmptySquare;
2124         board[i][countsColumn]   = (ChessSquare) 0;
2125     }
2126     while( (p=*holdings++) != NULLCHAR ) {
2127         piece = CharToPiece( ToUpper(p) );
2128         if(piece == EmptySquare) continue;
2129         /*j = (int) piece - (int) WhitePawn;*/
2130         j = PieceToNumber(piece);
2131         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2132         if(j < 0) continue;               /* should not happen */
2133         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2134         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2135         board[holdingsStartRow+j*direction][countsColumn]++;
2136     }
2137 }
2138
2139
2140 void
2141 VariantSwitch(Board board, VariantClass newVariant)
2142 {
2143    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2144    static Board oldBoard;
2145
2146    startedFromPositionFile = FALSE;
2147    if(gameInfo.variant == newVariant) return;
2148
2149    /* [HGM] This routine is called each time an assignment is made to
2150     * gameInfo.variant during a game, to make sure the board sizes
2151     * are set to match the new variant. If that means adding or deleting
2152     * holdings, we shift the playing board accordingly
2153     * This kludge is needed because in ICS observe mode, we get boards
2154     * of an ongoing game without knowing the variant, and learn about the
2155     * latter only later. This can be because of the move list we requested,
2156     * in which case the game history is refilled from the beginning anyway,
2157     * but also when receiving holdings of a crazyhouse game. In the latter
2158     * case we want to add those holdings to the already received position.
2159     */
2160
2161
2162    if (appData.debugMode) {
2163      fprintf(debugFP, "Switch board from %s to %s\n",
2164              VariantName(gameInfo.variant), VariantName(newVariant));
2165      setbuf(debugFP, NULL);
2166    }
2167    shuffleOpenings = 0;       /* [HGM] shuffle */
2168    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2169    switch(newVariant)
2170      {
2171      case VariantShogi:
2172        newWidth = 9;  newHeight = 9;
2173        gameInfo.holdingsSize = 7;
2174      case VariantBughouse:
2175      case VariantCrazyhouse:
2176        newHoldingsWidth = 2; break;
2177      case VariantGreat:
2178        newWidth = 10;
2179      case VariantSuper:
2180        newHoldingsWidth = 2;
2181        gameInfo.holdingsSize = 8;
2182        break;
2183      case VariantGothic:
2184      case VariantCapablanca:
2185      case VariantCapaRandom:
2186        newWidth = 10;
2187      default:
2188        newHoldingsWidth = gameInfo.holdingsSize = 0;
2189      };
2190
2191    if(newWidth  != gameInfo.boardWidth  ||
2192       newHeight != gameInfo.boardHeight ||
2193       newHoldingsWidth != gameInfo.holdingsWidth ) {
2194
2195      /* shift position to new playing area, if needed */
2196      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2197        for(i=0; i<BOARD_HEIGHT; i++)
2198          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2199            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2200              board[i][j];
2201        for(i=0; i<newHeight; i++) {
2202          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2203          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2204        }
2205      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2206        for(i=0; i<BOARD_HEIGHT; i++)
2207          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2208            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2209              board[i][j];
2210      }
2211      gameInfo.boardWidth  = newWidth;
2212      gameInfo.boardHeight = newHeight;
2213      gameInfo.holdingsWidth = newHoldingsWidth;
2214      gameInfo.variant = newVariant;
2215      InitDrawingSizes(-2, 0);
2216    } else gameInfo.variant = newVariant;
2217    CopyBoard(oldBoard, board);   // remember correctly formatted board
2218      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2219    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2220 }
2221
2222 static int loggedOn = FALSE;
2223
2224 /*-- Game start info cache: --*/
2225 int gs_gamenum;
2226 char gs_kind[MSG_SIZ];
2227 static char player1Name[128] = "";
2228 static char player2Name[128] = "";
2229 static char cont_seq[] = "\n\\   ";
2230 static int player1Rating = -1;
2231 static int player2Rating = -1;
2232 /*----------------------------*/
2233
2234 ColorClass curColor = ColorNormal;
2235 int suppressKibitz = 0;
2236
2237 // [HGM] seekgraph
2238 Boolean soughtPending = FALSE;
2239 Boolean seekGraphUp;
2240 #define MAX_SEEK_ADS 200
2241 #define SQUARE 0x80
2242 char *seekAdList[MAX_SEEK_ADS];
2243 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2244 float tcList[MAX_SEEK_ADS];
2245 char colorList[MAX_SEEK_ADS];
2246 int nrOfSeekAds = 0;
2247 int minRating = 1010, maxRating = 2800;
2248 int hMargin = 10, vMargin = 20, h, w;
2249 extern int squareSize, lineGap;
2250
2251 void
2252 PlotSeekAd(int i)
2253 {
2254         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2255         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2256         if(r < minRating+100 && r >=0 ) r = minRating+100;
2257         if(r > maxRating) r = maxRating;
2258         if(tc < 1.) tc = 1.;
2259         if(tc > 95.) tc = 95.;
2260         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2261         y = ((double)r - minRating)/(maxRating - minRating)
2262             * (h-vMargin-squareSize/8-1) + vMargin;
2263         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2264         if(strstr(seekAdList[i], " u ")) color = 1;
2265         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2266            !strstr(seekAdList[i], "bullet") &&
2267            !strstr(seekAdList[i], "blitz") &&
2268            !strstr(seekAdList[i], "standard") ) color = 2;
2269         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2270         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2271 }
2272
2273 void
2274 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2275 {
2276         char buf[MSG_SIZ], *ext = "";
2277         VariantClass v = StringToVariant(type);
2278         if(strstr(type, "wild")) {
2279             ext = type + 4; // append wild number
2280             if(v == VariantFischeRandom) type = "chess960"; else
2281             if(v == VariantLoadable) type = "setup"; else
2282             type = VariantName(v);
2283         }
2284         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2285         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2286             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2287             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2288             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2289             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2290             seekNrList[nrOfSeekAds] = nr;
2291             zList[nrOfSeekAds] = 0;
2292             seekAdList[nrOfSeekAds++] = StrSave(buf);
2293             if(plot) PlotSeekAd(nrOfSeekAds-1);
2294         }
2295 }
2296
2297 void
2298 EraseSeekDot(int i)
2299 {
2300     int x = xList[i], y = yList[i], d=squareSize/4, k;
2301     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2302     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2303     // now replot every dot that overlapped
2304     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2305         int xx = xList[k], yy = yList[k];
2306         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2307             DrawSeekDot(xx, yy, colorList[k]);
2308     }
2309 }
2310
2311 void
2312 RemoveSeekAd(int nr)
2313 {
2314         int i;
2315         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2316             EraseSeekDot(i);
2317             if(seekAdList[i]) free(seekAdList[i]);
2318             seekAdList[i] = seekAdList[--nrOfSeekAds];
2319             seekNrList[i] = seekNrList[nrOfSeekAds];
2320             ratingList[i] = ratingList[nrOfSeekAds];
2321             colorList[i]  = colorList[nrOfSeekAds];
2322             tcList[i] = tcList[nrOfSeekAds];
2323             xList[i]  = xList[nrOfSeekAds];
2324             yList[i]  = yList[nrOfSeekAds];
2325             zList[i]  = zList[nrOfSeekAds];
2326             seekAdList[nrOfSeekAds] = NULL;
2327             break;
2328         }
2329 }
2330
2331 Boolean
2332 MatchSoughtLine(char *line)
2333 {
2334     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2335     int nr, base, inc, u=0; char dummy;
2336
2337     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2338        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2339        (u=1) &&
2340        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2341         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2342         // match: compact and save the line
2343         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2344         return TRUE;
2345     }
2346     return FALSE;
2347 }
2348
2349 int
2350 DrawSeekGraph()
2351 {
2352     int i;
2353     if(!seekGraphUp) return FALSE;
2354     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2355     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2356
2357     DrawSeekBackground(0, 0, w, h);
2358     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2359     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2360     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2361         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2362         yy = h-1-yy;
2363         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2364         if(i%500 == 0) {
2365             char buf[MSG_SIZ];
2366             snprintf(buf, MSG_SIZ, "%d", i);
2367             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2368         }
2369     }
2370     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2371     for(i=1; i<100; i+=(i<10?1:5)) {
2372         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2373         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2374         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2375             char buf[MSG_SIZ];
2376             snprintf(buf, MSG_SIZ, "%d", i);
2377             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2378         }
2379     }
2380     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2381     return TRUE;
2382 }
2383
2384 int SeekGraphClick(ClickType click, int x, int y, int moving)
2385 {
2386     static int lastDown = 0, displayed = 0, lastSecond;
2387     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2388         if(click == Release || moving) return FALSE;
2389         nrOfSeekAds = 0;
2390         soughtPending = TRUE;
2391         SendToICS(ics_prefix);
2392         SendToICS("sought\n"); // should this be "sought all"?
2393     } else { // issue challenge based on clicked ad
2394         int dist = 10000; int i, closest = 0, second = 0;
2395         for(i=0; i<nrOfSeekAds; i++) {
2396             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2397             if(d < dist) { dist = d; closest = i; }
2398             second += (d - zList[i] < 120); // count in-range ads
2399             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2400         }
2401         if(dist < 120) {
2402             char buf[MSG_SIZ];
2403             second = (second > 1);
2404             if(displayed != closest || second != lastSecond) {
2405                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2406                 lastSecond = second; displayed = closest;
2407             }
2408             if(click == Press) {
2409                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2410                 lastDown = closest;
2411                 return TRUE;
2412             } // on press 'hit', only show info
2413             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2414             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2415             SendToICS(ics_prefix);
2416             SendToICS(buf);
2417             return TRUE; // let incoming board of started game pop down the graph
2418         } else if(click == Release) { // release 'miss' is ignored
2419             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2420             if(moving == 2) { // right up-click
2421                 nrOfSeekAds = 0; // refresh graph
2422                 soughtPending = TRUE;
2423                 SendToICS(ics_prefix);
2424                 SendToICS("sought\n"); // should this be "sought all"?
2425             }
2426             return TRUE;
2427         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2428         // press miss or release hit 'pop down' seek graph
2429         seekGraphUp = FALSE;
2430         DrawPosition(TRUE, NULL);
2431     }
2432     return TRUE;
2433 }
2434
2435 void
2436 read_from_ics(isr, closure, data, count, error)
2437      InputSourceRef isr;
2438      VOIDSTAR closure;
2439      char *data;
2440      int count;
2441      int error;
2442 {
2443 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2444 #define STARTED_NONE 0
2445 #define STARTED_MOVES 1
2446 #define STARTED_BOARD 2
2447 #define STARTED_OBSERVE 3
2448 #define STARTED_HOLDINGS 4
2449 #define STARTED_CHATTER 5
2450 #define STARTED_COMMENT 6
2451 #define STARTED_MOVES_NOHIDE 7
2452
2453     static int started = STARTED_NONE;
2454     static char parse[20000];
2455     static int parse_pos = 0;
2456     static char buf[BUF_SIZE + 1];
2457     static int firstTime = TRUE, intfSet = FALSE;
2458     static ColorClass prevColor = ColorNormal;
2459     static int savingComment = FALSE;
2460     static int cmatch = 0; // continuation sequence match
2461     char *bp;
2462     char str[MSG_SIZ];
2463     int i, oldi;
2464     int buf_len;
2465     int next_out;
2466     int tkind;
2467     int backup;    /* [DM] For zippy color lines */
2468     char *p;
2469     char talker[MSG_SIZ]; // [HGM] chat
2470     int channel;
2471
2472     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2473
2474     if (appData.debugMode) {
2475       if (!error) {
2476         fprintf(debugFP, "<ICS: ");
2477         show_bytes(debugFP, data, count);
2478         fprintf(debugFP, "\n");
2479       }
2480     }
2481
2482     if (appData.debugMode) { int f = forwardMostMove;
2483         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2484                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2485                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2486     }
2487     if (count > 0) {
2488         /* If last read ended with a partial line that we couldn't parse,
2489            prepend it to the new read and try again. */
2490         if (leftover_len > 0) {
2491             for (i=0; i<leftover_len; i++)
2492               buf[i] = buf[leftover_start + i];
2493         }
2494
2495     /* copy new characters into the buffer */
2496     bp = buf + leftover_len;
2497     buf_len=leftover_len;
2498     for (i=0; i<count; i++)
2499     {
2500         // ignore these
2501         if (data[i] == '\r')
2502             continue;
2503
2504         // join lines split by ICS?
2505         if (!appData.noJoin)
2506         {
2507             /*
2508                 Joining just consists of finding matches against the
2509                 continuation sequence, and discarding that sequence
2510                 if found instead of copying it.  So, until a match
2511                 fails, there's nothing to do since it might be the
2512                 complete sequence, and thus, something we don't want
2513                 copied.
2514             */
2515             if (data[i] == cont_seq[cmatch])
2516             {
2517                 cmatch++;
2518                 if (cmatch == strlen(cont_seq))
2519                 {
2520                     cmatch = 0; // complete match.  just reset the counter
2521
2522                     /*
2523                         it's possible for the ICS to not include the space
2524                         at the end of the last word, making our [correct]
2525                         join operation fuse two separate words.  the server
2526                         does this when the space occurs at the width setting.
2527                     */
2528                     if (!buf_len || buf[buf_len-1] != ' ')
2529                     {
2530                         *bp++ = ' ';
2531                         buf_len++;
2532                     }
2533                 }
2534                 continue;
2535             }
2536             else if (cmatch)
2537             {
2538                 /*
2539                     match failed, so we have to copy what matched before
2540                     falling through and copying this character.  In reality,
2541                     this will only ever be just the newline character, but
2542                     it doesn't hurt to be precise.
2543                 */
2544                 strncpy(bp, cont_seq, cmatch);
2545                 bp += cmatch;
2546                 buf_len += cmatch;
2547                 cmatch = 0;
2548             }
2549         }
2550
2551         // copy this char
2552         *bp++ = data[i];
2553         buf_len++;
2554     }
2555
2556         buf[buf_len] = NULLCHAR;
2557 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2558         next_out = 0;
2559         leftover_start = 0;
2560
2561         i = 0;
2562         while (i < buf_len) {
2563             /* Deal with part of the TELNET option negotiation
2564                protocol.  We refuse to do anything beyond the
2565                defaults, except that we allow the WILL ECHO option,
2566                which ICS uses to turn off password echoing when we are
2567                directly connected to it.  We reject this option
2568                if localLineEditing mode is on (always on in xboard)
2569                and we are talking to port 23, which might be a real
2570                telnet server that will try to keep WILL ECHO on permanently.
2571              */
2572             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2573                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2574                 unsigned char option;
2575                 oldi = i;
2576                 switch ((unsigned char) buf[++i]) {
2577                   case TN_WILL:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<WILL ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       case TN_ECHO:
2582                         if (appData.debugMode)
2583                           fprintf(debugFP, "ECHO ");
2584                         /* Reply only if this is a change, according
2585                            to the protocol rules. */
2586                         if (remoteEchoOption) break;
2587                         if (appData.localLineEditing &&
2588                             atoi(appData.icsPort) == TN_PORT) {
2589                             TelnetRequest(TN_DONT, TN_ECHO);
2590                         } else {
2591                             EchoOff();
2592                             TelnetRequest(TN_DO, TN_ECHO);
2593                             remoteEchoOption = TRUE;
2594                         }
2595                         break;
2596                       default:
2597                         if (appData.debugMode)
2598                           fprintf(debugFP, "%d ", option);
2599                         /* Whatever this is, we don't want it. */
2600                         TelnetRequest(TN_DONT, option);
2601                         break;
2602                     }
2603                     break;
2604                   case TN_WONT:
2605                     if (appData.debugMode)
2606                       fprintf(debugFP, "\n<WONT ");
2607                     switch (option = (unsigned char) buf[++i]) {
2608                       case TN_ECHO:
2609                         if (appData.debugMode)
2610                           fprintf(debugFP, "ECHO ");
2611                         /* Reply only if this is a change, according
2612                            to the protocol rules. */
2613                         if (!remoteEchoOption) break;
2614                         EchoOn();
2615                         TelnetRequest(TN_DONT, TN_ECHO);
2616                         remoteEchoOption = FALSE;
2617                         break;
2618                       default:
2619                         if (appData.debugMode)
2620                           fprintf(debugFP, "%d ", (unsigned char) option);
2621                         /* Whatever this is, it must already be turned
2622                            off, because we never agree to turn on
2623                            anything non-default, so according to the
2624                            protocol rules, we don't reply. */
2625                         break;
2626                     }
2627                     break;
2628                   case TN_DO:
2629                     if (appData.debugMode)
2630                       fprintf(debugFP, "\n<DO ");
2631                     switch (option = (unsigned char) buf[++i]) {
2632                       default:
2633                         /* Whatever this is, we refuse to do it. */
2634                         if (appData.debugMode)
2635                           fprintf(debugFP, "%d ", option);
2636                         TelnetRequest(TN_WONT, option);
2637                         break;
2638                     }
2639                     break;
2640                   case TN_DONT:
2641                     if (appData.debugMode)
2642                       fprintf(debugFP, "\n<DONT ");
2643                     switch (option = (unsigned char) buf[++i]) {
2644                       default:
2645                         if (appData.debugMode)
2646                           fprintf(debugFP, "%d ", option);
2647                         /* Whatever this is, we are already not doing
2648                            it, because we never agree to do anything
2649                            non-default, so according to the protocol
2650                            rules, we don't reply. */
2651                         break;
2652                     }
2653                     break;
2654                   case TN_IAC:
2655                     if (appData.debugMode)
2656                       fprintf(debugFP, "\n<IAC ");
2657                     /* Doubled IAC; pass it through */
2658                     i--;
2659                     break;
2660                   default:
2661                     if (appData.debugMode)
2662                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2663                     /* Drop all other telnet commands on the floor */
2664                     break;
2665                 }
2666                 if (oldi > next_out)
2667                   SendToPlayer(&buf[next_out], oldi - next_out);
2668                 if (++i > next_out)
2669                   next_out = i;
2670                 continue;
2671             }
2672
2673             /* OK, this at least will *usually* work */
2674             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2675                 loggedOn = TRUE;
2676             }
2677
2678             if (loggedOn && !intfSet) {
2679                 if (ics_type == ICS_ICC) {
2680                   snprintf(str, MSG_SIZ,
2681                           "/set-quietly interface %s\n/set-quietly style 12\n",
2682                           programVersion);
2683                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2684                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2685                 } else if (ics_type == ICS_CHESSNET) {
2686                   snprintf(str, MSG_SIZ, "/style 12\n");
2687                 } else {
2688                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2689                   strcat(str, programVersion);
2690                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2691                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2692                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2693 #ifdef WIN32
2694                   strcat(str, "$iset nohighlight 1\n");
2695 #endif
2696                   strcat(str, "$iset lock 1\n$style 12\n");
2697                 }
2698                 SendToICS(str);
2699                 NotifyFrontendLogin();
2700                 intfSet = TRUE;
2701             }
2702
2703             if (started == STARTED_COMMENT) {
2704                 /* Accumulate characters in comment */
2705                 parse[parse_pos++] = buf[i];
2706                 if (buf[i] == '\n') {
2707                     parse[parse_pos] = NULLCHAR;
2708                     if(chattingPartner>=0) {
2709                         char mess[MSG_SIZ];
2710                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2711                         OutputChatMessage(chattingPartner, mess);
2712                         chattingPartner = -1;
2713                         next_out = i+1; // [HGM] suppress printing in ICS window
2714                     } else
2715                     if(!suppressKibitz) // [HGM] kibitz
2716                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2717                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2718                         int nrDigit = 0, nrAlph = 0, j;
2719                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2720                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2721                         parse[parse_pos] = NULLCHAR;
2722                         // try to be smart: if it does not look like search info, it should go to
2723                         // ICS interaction window after all, not to engine-output window.
2724                         for(j=0; j<parse_pos; j++) { // count letters and digits
2725                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2726                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2727                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2728                         }
2729                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2730                             int depth=0; float score;
2731                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2732                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2733                                 pvInfoList[forwardMostMove-1].depth = depth;
2734                                 pvInfoList[forwardMostMove-1].score = 100*score;
2735                             }
2736                             OutputKibitz(suppressKibitz, parse);
2737                         } else {
2738                             char tmp[MSG_SIZ];
2739                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2740                             SendToPlayer(tmp, strlen(tmp));
2741                         }
2742                         next_out = i+1; // [HGM] suppress printing in ICS window
2743                     }
2744                     started = STARTED_NONE;
2745                 } else {
2746                     /* Don't match patterns against characters in comment */
2747                     i++;
2748                     continue;
2749                 }
2750             }
2751             if (started == STARTED_CHATTER) {
2752                 if (buf[i] != '\n') {
2753                     /* Don't match patterns against characters in chatter */
2754                     i++;
2755                     continue;
2756                 }
2757                 started = STARTED_NONE;
2758                 if(suppressKibitz) next_out = i+1;
2759             }
2760
2761             /* Kludge to deal with rcmd protocol */
2762             if (firstTime && looking_at(buf, &i, "\001*")) {
2763                 DisplayFatalError(&buf[1], 0, 1);
2764                 continue;
2765             } else {
2766                 firstTime = FALSE;
2767             }
2768
2769             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2770                 ics_type = ICS_ICC;
2771                 ics_prefix = "/";
2772                 if (appData.debugMode)
2773                   fprintf(debugFP, "ics_type %d\n", ics_type);
2774                 continue;
2775             }
2776             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2777                 ics_type = ICS_FICS;
2778                 ics_prefix = "$";
2779                 if (appData.debugMode)
2780                   fprintf(debugFP, "ics_type %d\n", ics_type);
2781                 continue;
2782             }
2783             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2784                 ics_type = ICS_CHESSNET;
2785                 ics_prefix = "/";
2786                 if (appData.debugMode)
2787                   fprintf(debugFP, "ics_type %d\n", ics_type);
2788                 continue;
2789             }
2790
2791             if (!loggedOn &&
2792                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2793                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2794                  looking_at(buf, &i, "will be \"*\""))) {
2795               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2796               continue;
2797             }
2798
2799             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2800               char buf[MSG_SIZ];
2801               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2802               DisplayIcsInteractionTitle(buf);
2803               have_set_title = TRUE;
2804             }
2805
2806             /* skip finger notes */
2807             if (started == STARTED_NONE &&
2808                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2809                  (buf[i] == '1' && buf[i+1] == '0')) &&
2810                 buf[i+2] == ':' && buf[i+3] == ' ') {
2811               started = STARTED_CHATTER;
2812               i += 3;
2813               continue;
2814             }
2815
2816             oldi = i;
2817             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2818             if(appData.seekGraph) {
2819                 if(soughtPending && MatchSoughtLine(buf+i)) {
2820                     i = strstr(buf+i, "rated") - buf;
2821                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2822                     next_out = leftover_start = i;
2823                     started = STARTED_CHATTER;
2824                     suppressKibitz = TRUE;
2825                     continue;
2826                 }
2827                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2828                         && looking_at(buf, &i, "* ads displayed")) {
2829                     soughtPending = FALSE;
2830                     seekGraphUp = TRUE;
2831                     DrawSeekGraph();
2832                     continue;
2833                 }
2834                 if(appData.autoRefresh) {
2835                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2836                         int s = (ics_type == ICS_ICC); // ICC format differs
2837                         if(seekGraphUp)
2838                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2839                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2840                         looking_at(buf, &i, "*% "); // eat prompt
2841                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2842                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2843                         next_out = i; // suppress
2844                         continue;
2845                     }
2846                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2847                         char *p = star_match[0];
2848                         while(*p) {
2849                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2850                             while(*p && *p++ != ' '); // next
2851                         }
2852                         looking_at(buf, &i, "*% "); // eat prompt
2853                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2854                         next_out = i;
2855                         continue;
2856                     }
2857                 }
2858             }
2859
2860             /* skip formula vars */
2861             if (started == STARTED_NONE &&
2862                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2863               started = STARTED_CHATTER;
2864               i += 3;
2865               continue;
2866             }
2867
2868             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2869             if (appData.autoKibitz && started == STARTED_NONE &&
2870                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2871                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2872                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2873                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2874                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2875                         suppressKibitz = TRUE;
2876                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2877                         next_out = i;
2878                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2879                                 && (gameMode == IcsPlayingWhite)) ||
2880                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2881                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2882                             started = STARTED_CHATTER; // own kibitz we simply discard
2883                         else {
2884                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2885                             parse_pos = 0; parse[0] = NULLCHAR;
2886                             savingComment = TRUE;
2887                             suppressKibitz = gameMode != IcsObserving ? 2 :
2888                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2889                         }
2890                         continue;
2891                 } else
2892                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2893                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2894                          && atoi(star_match[0])) {
2895                     // suppress the acknowledgements of our own autoKibitz
2896                     char *p;
2897                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2898                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2899                     SendToPlayer(star_match[0], strlen(star_match[0]));
2900                     if(looking_at(buf, &i, "*% ")) // eat prompt
2901                         suppressKibitz = FALSE;
2902                     next_out = i;
2903                     continue;
2904                 }
2905             } // [HGM] kibitz: end of patch
2906
2907             // [HGM] chat: intercept tells by users for which we have an open chat window
2908             channel = -1;
2909             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2910                                            looking_at(buf, &i, "* whispers:") ||
2911                                            looking_at(buf, &i, "* kibitzes:") ||
2912                                            looking_at(buf, &i, "* shouts:") ||
2913                                            looking_at(buf, &i, "* c-shouts:") ||
2914                                            looking_at(buf, &i, "--> * ") ||
2915                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2916                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2917                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2918                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2919                 int p;
2920                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2921                 chattingPartner = -1;
2922
2923                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2924                 for(p=0; p<MAX_CHAT; p++) {
2925                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2926                     talker[0] = '['; strcat(talker, "] ");
2927                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2928                     chattingPartner = p; break;
2929                     }
2930                 } else
2931                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2932                 for(p=0; p<MAX_CHAT; p++) {
2933                     if(!strcmp("kibitzes", chatPartner[p])) {
2934                         talker[0] = '['; strcat(talker, "] ");
2935                         chattingPartner = p; break;
2936                     }
2937                 } else
2938                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2939                 for(p=0; p<MAX_CHAT; p++) {
2940                     if(!strcmp("whispers", chatPartner[p])) {
2941                         talker[0] = '['; strcat(talker, "] ");
2942                         chattingPartner = p; break;
2943                     }
2944                 } else
2945                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2946                   if(buf[i-8] == '-' && buf[i-3] == 't')
2947                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2948                     if(!strcmp("c-shouts", chatPartner[p])) {
2949                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2950                         chattingPartner = p; break;
2951                     }
2952                   }
2953                   if(chattingPartner < 0)
2954                   for(p=0; p<MAX_CHAT; p++) {
2955                     if(!strcmp("shouts", chatPartner[p])) {
2956                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2957                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2958                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2959                         chattingPartner = p; break;
2960                     }
2961                   }
2962                 }
2963                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2964                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2965                     talker[0] = 0; Colorize(ColorTell, FALSE);
2966                     chattingPartner = p; break;
2967                 }
2968                 if(chattingPartner<0) i = oldi; else {
2969                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2970                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2971                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2972                     started = STARTED_COMMENT;
2973                     parse_pos = 0; parse[0] = NULLCHAR;
2974                     savingComment = 3 + chattingPartner; // counts as TRUE
2975                     suppressKibitz = TRUE;
2976                     continue;
2977                 }
2978             } // [HGM] chat: end of patch
2979
2980             if (appData.zippyTalk || appData.zippyPlay) {
2981                 /* [DM] Backup address for color zippy lines */
2982                 backup = i;
2983 #if ZIPPY
2984                if (loggedOn == TRUE)
2985                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2986                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2987 #endif
2988             } // [DM] 'else { ' deleted
2989                 if (
2990                     /* Regular tells and says */
2991                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2992                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2993                     looking_at(buf, &i, "* says: ") ||
2994                     /* Don't color "message" or "messages" output */
2995                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2996                     looking_at(buf, &i, "*. * at *:*: ") ||
2997                     looking_at(buf, &i, "--* (*:*): ") ||
2998                     /* Message notifications (same color as tells) */
2999                     looking_at(buf, &i, "* has left a message ") ||
3000                     looking_at(buf, &i, "* just sent you a message:\n") ||
3001                     /* Whispers and kibitzes */
3002                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3003                     looking_at(buf, &i, "* kibitzes: ") ||
3004                     /* Channel tells */
3005                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3006
3007                   if (tkind == 1 && strchr(star_match[0], ':')) {
3008                       /* Avoid "tells you:" spoofs in channels */
3009                      tkind = 3;
3010                   }
3011                   if (star_match[0][0] == NULLCHAR ||
3012                       strchr(star_match[0], ' ') ||
3013                       (tkind == 3 && strchr(star_match[1], ' '))) {
3014                     /* Reject bogus matches */
3015                     i = oldi;
3016                   } else {
3017                     if (appData.colorize) {
3018                       if (oldi > next_out) {
3019                         SendToPlayer(&buf[next_out], oldi - next_out);
3020                         next_out = oldi;
3021                       }
3022                       switch (tkind) {
3023                       case 1:
3024                         Colorize(ColorTell, FALSE);
3025                         curColor = ColorTell;
3026                         break;
3027                       case 2:
3028                         Colorize(ColorKibitz, FALSE);
3029                         curColor = ColorKibitz;
3030                         break;
3031                       case 3:
3032                         p = strrchr(star_match[1], '(');
3033                         if (p == NULL) {
3034                           p = star_match[1];
3035                         } else {
3036                           p++;
3037                         }
3038                         if (atoi(p) == 1) {
3039                           Colorize(ColorChannel1, FALSE);
3040                           curColor = ColorChannel1;
3041                         } else {
3042                           Colorize(ColorChannel, FALSE);
3043                           curColor = ColorChannel;
3044                         }
3045                         break;
3046                       case 5:
3047                         curColor = ColorNormal;
3048                         break;
3049                       }
3050                     }
3051                     if (started == STARTED_NONE && appData.autoComment &&
3052                         (gameMode == IcsObserving ||
3053                          gameMode == IcsPlayingWhite ||
3054                          gameMode == IcsPlayingBlack)) {
3055                       parse_pos = i - oldi;
3056                       memcpy(parse, &buf[oldi], parse_pos);
3057                       parse[parse_pos] = NULLCHAR;
3058                       started = STARTED_COMMENT;
3059                       savingComment = TRUE;
3060                     } else {
3061                       started = STARTED_CHATTER;
3062                       savingComment = FALSE;
3063                     }
3064                     loggedOn = TRUE;
3065                     continue;
3066                   }
3067                 }
3068
3069                 if (looking_at(buf, &i, "* s-shouts: ") ||
3070                     looking_at(buf, &i, "* c-shouts: ")) {
3071                     if (appData.colorize) {
3072                         if (oldi > next_out) {
3073                             SendToPlayer(&buf[next_out], oldi - next_out);
3074                             next_out = oldi;
3075                         }
3076                         Colorize(ColorSShout, FALSE);
3077                         curColor = ColorSShout;
3078                     }
3079                     loggedOn = TRUE;
3080                     started = STARTED_CHATTER;
3081                     continue;
3082                 }
3083
3084                 if (looking_at(buf, &i, "--->")) {
3085                     loggedOn = TRUE;
3086                     continue;
3087                 }
3088
3089                 if (looking_at(buf, &i, "* shouts: ") ||
3090                     looking_at(buf, &i, "--> ")) {
3091                     if (appData.colorize) {
3092                         if (oldi > next_out) {
3093                             SendToPlayer(&buf[next_out], oldi - next_out);
3094                             next_out = oldi;
3095                         }
3096                         Colorize(ColorShout, FALSE);
3097                         curColor = ColorShout;
3098                     }
3099                     loggedOn = TRUE;
3100                     started = STARTED_CHATTER;
3101                     continue;
3102                 }
3103
3104                 if (looking_at( buf, &i, "Challenge:")) {
3105                     if (appData.colorize) {
3106                         if (oldi > next_out) {
3107                             SendToPlayer(&buf[next_out], oldi - next_out);
3108                             next_out = oldi;
3109                         }
3110                         Colorize(ColorChallenge, FALSE);
3111                         curColor = ColorChallenge;
3112                     }
3113                     loggedOn = TRUE;
3114                     continue;
3115                 }
3116
3117                 if (looking_at(buf, &i, "* offers you") ||
3118                     looking_at(buf, &i, "* offers to be") ||
3119                     looking_at(buf, &i, "* would like to") ||
3120                     looking_at(buf, &i, "* requests to") ||
3121                     looking_at(buf, &i, "Your opponent offers") ||
3122                     looking_at(buf, &i, "Your opponent requests")) {
3123
3124                     if (appData.colorize) {
3125                         if (oldi > next_out) {
3126                             SendToPlayer(&buf[next_out], oldi - next_out);
3127                             next_out = oldi;
3128                         }
3129                         Colorize(ColorRequest, FALSE);
3130                         curColor = ColorRequest;
3131                     }
3132                     continue;
3133                 }
3134
3135                 if (looking_at(buf, &i, "* (*) seeking")) {
3136                     if (appData.colorize) {
3137                         if (oldi > next_out) {
3138                             SendToPlayer(&buf[next_out], oldi - next_out);
3139                             next_out = oldi;
3140                         }
3141                         Colorize(ColorSeek, FALSE);
3142                         curColor = ColorSeek;
3143                     }
3144                     continue;
3145             }
3146
3147             if (looking_at(buf, &i, "\\   ")) {
3148                 if (prevColor != ColorNormal) {
3149                     if (oldi > next_out) {
3150                         SendToPlayer(&buf[next_out], oldi - next_out);
3151                         next_out = oldi;
3152                     }
3153                     Colorize(prevColor, TRUE);
3154                     curColor = prevColor;
3155                 }
3156                 if (savingComment) {
3157                     parse_pos = i - oldi;
3158                     memcpy(parse, &buf[oldi], parse_pos);
3159                     parse[parse_pos] = NULLCHAR;
3160                     started = STARTED_COMMENT;
3161                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3162                         chattingPartner = savingComment - 3; // kludge to remember the box
3163                 } else {
3164                     started = STARTED_CHATTER;
3165                 }
3166                 continue;
3167             }
3168
3169             if (looking_at(buf, &i, "Black Strength :") ||
3170                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3171                 looking_at(buf, &i, "<10>") ||
3172                 looking_at(buf, &i, "#@#")) {
3173                 /* Wrong board style */
3174                 loggedOn = TRUE;
3175                 SendToICS(ics_prefix);
3176                 SendToICS("set style 12\n");
3177                 SendToICS(ics_prefix);
3178                 SendToICS("refresh\n");
3179                 continue;
3180             }
3181
3182             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3183                 ICSInitScript();
3184                 have_sent_ICS_logon = 1;
3185                 continue;
3186             }
3187
3188             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3189                 (looking_at(buf, &i, "\n<12> ") ||
3190                  looking_at(buf, &i, "<12> "))) {
3191                 loggedOn = TRUE;
3192                 if (oldi > next_out) {
3193                     SendToPlayer(&buf[next_out], oldi - next_out);
3194                 }
3195                 next_out = i;
3196                 started = STARTED_BOARD;
3197                 parse_pos = 0;
3198                 continue;
3199             }
3200
3201             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3202                 looking_at(buf, &i, "<b1> ")) {
3203                 if (oldi > next_out) {
3204                     SendToPlayer(&buf[next_out], oldi - next_out);
3205                 }
3206                 next_out = i;
3207                 started = STARTED_HOLDINGS;
3208                 parse_pos = 0;
3209                 continue;
3210             }
3211
3212             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3213                 loggedOn = TRUE;
3214                 /* Header for a move list -- first line */
3215
3216                 switch (ics_getting_history) {
3217                   case H_FALSE:
3218                     switch (gameMode) {
3219                       case IcsIdle:
3220                       case BeginningOfGame:
3221                         /* User typed "moves" or "oldmoves" while we
3222                            were idle.  Pretend we asked for these
3223                            moves and soak them up so user can step
3224                            through them and/or save them.
3225                            */
3226                         Reset(FALSE, TRUE);
3227                         gameMode = IcsObserving;
3228                         ModeHighlight();
3229                         ics_gamenum = -1;
3230                         ics_getting_history = H_GOT_UNREQ_HEADER;
3231                         break;
3232                       case EditGame: /*?*/
3233                       case EditPosition: /*?*/
3234                         /* Should above feature work in these modes too? */
3235                         /* For now it doesn't */
3236                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3237                         break;
3238                       default:
3239                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3240                         break;
3241                     }
3242                     break;
3243                   case H_REQUESTED:
3244                     /* Is this the right one? */
3245                     if (gameInfo.white && gameInfo.black &&
3246                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3247                         strcmp(gameInfo.black, star_match[2]) == 0) {
3248                         /* All is well */
3249                         ics_getting_history = H_GOT_REQ_HEADER;
3250                     }
3251                     break;
3252                   case H_GOT_REQ_HEADER:
3253                   case H_GOT_UNREQ_HEADER:
3254                   case H_GOT_UNWANTED_HEADER:
3255                   case H_GETTING_MOVES:
3256                     /* Should not happen */
3257                     DisplayError(_("Error gathering move list: two headers"), 0);
3258                     ics_getting_history = H_FALSE;
3259                     break;
3260                 }
3261
3262                 /* Save player ratings into gameInfo if needed */
3263                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3264                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3265                     (gameInfo.whiteRating == -1 ||
3266                      gameInfo.blackRating == -1)) {
3267
3268                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3269                     gameInfo.blackRating = string_to_rating(star_match[3]);
3270                     if (appData.debugMode)
3271                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3272                               gameInfo.whiteRating, gameInfo.blackRating);
3273                 }
3274                 continue;
3275             }
3276
3277             if (looking_at(buf, &i,
3278               "* * match, initial time: * minute*, increment: * second")) {
3279                 /* Header for a move list -- second line */
3280                 /* Initial board will follow if this is a wild game */
3281                 if (gameInfo.event != NULL) free(gameInfo.event);
3282                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3283                 gameInfo.event = StrSave(str);
3284                 /* [HGM] we switched variant. Translate boards if needed. */
3285                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3286                 continue;
3287             }
3288
3289             if (looking_at(buf, &i, "Move  ")) {
3290                 /* Beginning of a move list */
3291                 switch (ics_getting_history) {
3292                   case H_FALSE:
3293                     /* Normally should not happen */
3294                     /* Maybe user hit reset while we were parsing */
3295                     break;
3296                   case H_REQUESTED:
3297                     /* Happens if we are ignoring a move list that is not
3298                      * the one we just requested.  Common if the user
3299                      * tries to observe two games without turning off
3300                      * getMoveList */
3301                     break;
3302                   case H_GETTING_MOVES:
3303                     /* Should not happen */
3304                     DisplayError(_("Error gathering move list: nested"), 0);
3305                     ics_getting_history = H_FALSE;
3306                     break;
3307                   case H_GOT_REQ_HEADER:
3308                     ics_getting_history = H_GETTING_MOVES;
3309                     started = STARTED_MOVES;
3310                     parse_pos = 0;
3311                     if (oldi > next_out) {
3312                         SendToPlayer(&buf[next_out], oldi - next_out);
3313                     }
3314                     break;
3315                   case H_GOT_UNREQ_HEADER:
3316                     ics_getting_history = H_GETTING_MOVES;
3317                     started = STARTED_MOVES_NOHIDE;
3318                     parse_pos = 0;
3319                     break;
3320                   case H_GOT_UNWANTED_HEADER:
3321                     ics_getting_history = H_FALSE;
3322                     break;
3323                 }
3324                 continue;
3325             }
3326
3327             if (looking_at(buf, &i, "% ") ||
3328                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3329                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3330                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3331                     soughtPending = FALSE;
3332                     seekGraphUp = TRUE;
3333                     DrawSeekGraph();
3334                 }
3335                 if(suppressKibitz) next_out = i;
3336                 savingComment = FALSE;
3337                 suppressKibitz = 0;
3338                 switch (started) {
3339                   case STARTED_MOVES:
3340                   case STARTED_MOVES_NOHIDE:
3341                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3342                     parse[parse_pos + i - oldi] = NULLCHAR;
3343                     ParseGameHistory(parse);
3344 #if ZIPPY
3345                     if (appData.zippyPlay && first.initDone) {
3346                         FeedMovesToProgram(&first, forwardMostMove);
3347                         if (gameMode == IcsPlayingWhite) {
3348                             if (WhiteOnMove(forwardMostMove)) {
3349                                 if (first.sendTime) {
3350                                   if (first.useColors) {
3351                                     SendToProgram("black\n", &first);
3352                                   }
3353                                   SendTimeRemaining(&first, TRUE);
3354                                 }
3355                                 if (first.useColors) {
3356                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3357                                 }
3358                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3359                                 first.maybeThinking = TRUE;
3360                             } else {
3361                                 if (first.usePlayother) {
3362                                   if (first.sendTime) {
3363                                     SendTimeRemaining(&first, TRUE);
3364                                   }
3365                                   SendToProgram("playother\n", &first);
3366                                   firstMove = FALSE;
3367                                 } else {
3368                                   firstMove = TRUE;
3369                                 }
3370                             }
3371                         } else if (gameMode == IcsPlayingBlack) {
3372                             if (!WhiteOnMove(forwardMostMove)) {
3373                                 if (first.sendTime) {
3374                                   if (first.useColors) {
3375                                     SendToProgram("white\n", &first);
3376                                   }
3377                                   SendTimeRemaining(&first, FALSE);
3378                                 }
3379                                 if (first.useColors) {
3380                                   SendToProgram("black\n", &first);
3381                                 }
3382                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3383                                 first.maybeThinking = TRUE;
3384                             } else {
3385                                 if (first.usePlayother) {
3386                                   if (first.sendTime) {
3387                                     SendTimeRemaining(&first, FALSE);
3388                                   }
3389                                   SendToProgram("playother\n", &first);
3390                                   firstMove = FALSE;
3391                                 } else {
3392                                   firstMove = TRUE;
3393                                 }
3394                             }
3395                         }
3396                     }
3397 #endif
3398                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3399                         /* Moves came from oldmoves or moves command
3400                            while we weren't doing anything else.
3401                            */
3402                         currentMove = forwardMostMove;
3403                         ClearHighlights();/*!!could figure this out*/
3404                         flipView = appData.flipView;
3405                         DrawPosition(TRUE, boards[currentMove]);
3406                         DisplayBothClocks();
3407                         snprintf(str, MSG_SIZ, "%s vs. %s",
3408                                 gameInfo.white, gameInfo.black);
3409                         DisplayTitle(str);
3410                         gameMode = IcsIdle;
3411                     } else {
3412                         /* Moves were history of an active game */
3413                         if (gameInfo.resultDetails != NULL) {
3414                             free(gameInfo.resultDetails);
3415                             gameInfo.resultDetails = NULL;
3416                         }
3417                     }
3418                     HistorySet(parseList, backwardMostMove,
3419                                forwardMostMove, currentMove-1);
3420                     DisplayMove(currentMove - 1);
3421                     if (started == STARTED_MOVES) next_out = i;
3422                     started = STARTED_NONE;
3423                     ics_getting_history = H_FALSE;
3424                     break;
3425
3426                   case STARTED_OBSERVE:
3427                     started = STARTED_NONE;
3428                     SendToICS(ics_prefix);
3429                     SendToICS("refresh\n");
3430                     break;
3431
3432                   default:
3433                     break;
3434                 }
3435                 if(bookHit) { // [HGM] book: simulate book reply
3436                     static char bookMove[MSG_SIZ]; // a bit generous?
3437
3438                     programStats.nodes = programStats.depth = programStats.time =
3439                     programStats.score = programStats.got_only_move = 0;
3440                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3441
3442                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3443                     strcat(bookMove, bookHit);
3444                     HandleMachineMove(bookMove, &first);
3445                 }
3446                 continue;
3447             }
3448
3449             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3450                  started == STARTED_HOLDINGS ||
3451                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3452                 /* Accumulate characters in move list or board */
3453                 parse[parse_pos++] = buf[i];
3454             }
3455
3456             /* Start of game messages.  Mostly we detect start of game
3457                when the first board image arrives.  On some versions
3458                of the ICS, though, we need to do a "refresh" after starting
3459                to observe in order to get the current board right away. */
3460             if (looking_at(buf, &i, "Adding game * to observation list")) {
3461                 started = STARTED_OBSERVE;
3462                 continue;
3463             }
3464
3465             /* Handle auto-observe */
3466             if (appData.autoObserve &&
3467                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3468                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3469                 char *player;
3470                 /* Choose the player that was highlighted, if any. */
3471                 if (star_match[0][0] == '\033' ||
3472                     star_match[1][0] != '\033') {
3473                     player = star_match[0];
3474                 } else {
3475                     player = star_match[2];
3476                 }
3477                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3478                         ics_prefix, StripHighlightAndTitle(player));
3479                 SendToICS(str);
3480
3481                 /* Save ratings from notify string */
3482                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3483                 player1Rating = string_to_rating(star_match[1]);
3484                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3485                 player2Rating = string_to_rating(star_match[3]);
3486
3487                 if (appData.debugMode)
3488                   fprintf(debugFP,
3489                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3490                           player1Name, player1Rating,
3491                           player2Name, player2Rating);
3492
3493                 continue;
3494             }
3495
3496             /* Deal with automatic examine mode after a game,
3497                and with IcsObserving -> IcsExamining transition */
3498             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3499                 looking_at(buf, &i, "has made you an examiner of game *")) {
3500
3501                 int gamenum = atoi(star_match[0]);
3502                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3503                     gamenum == ics_gamenum) {
3504                     /* We were already playing or observing this game;
3505                        no need to refetch history */
3506                     gameMode = IcsExamining;
3507                     if (pausing) {
3508                         pauseExamForwardMostMove = forwardMostMove;
3509                     } else if (currentMove < forwardMostMove) {
3510                         ForwardInner(forwardMostMove);
3511                     }
3512                 } else {
3513                     /* I don't think this case really can happen */
3514                     SendToICS(ics_prefix);
3515                     SendToICS("refresh\n");
3516                 }
3517                 continue;
3518             }
3519
3520             /* Error messages */
3521 //          if (ics_user_moved) {
3522             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3523                 if (looking_at(buf, &i, "Illegal move") ||
3524                     looking_at(buf, &i, "Not a legal move") ||
3525                     looking_at(buf, &i, "Your king is in check") ||
3526                     looking_at(buf, &i, "It isn't your turn") ||
3527                     looking_at(buf, &i, "It is not your move")) {
3528                     /* Illegal move */
3529                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3530                         currentMove = forwardMostMove-1;
3531                         DisplayMove(currentMove - 1); /* before DMError */
3532                         DrawPosition(FALSE, boards[currentMove]);
3533                         SwitchClocks(forwardMostMove-1); // [HGM] race
3534                         DisplayBothClocks();
3535                     }
3536                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3537                     ics_user_moved = 0;
3538                     continue;
3539                 }
3540             }
3541
3542             if (looking_at(buf, &i, "still have time") ||
3543                 looking_at(buf, &i, "not out of time") ||
3544                 looking_at(buf, &i, "either player is out of time") ||
3545                 looking_at(buf, &i, "has timeseal; checking")) {
3546                 /* We must have called his flag a little too soon */
3547                 whiteFlag = blackFlag = FALSE;
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "added * seconds to") ||
3552                 looking_at(buf, &i, "seconds were added to")) {
3553                 /* Update the clocks */
3554                 SendToICS(ics_prefix);
3555                 SendToICS("refresh\n");
3556                 continue;
3557             }
3558
3559             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3560                 ics_clock_paused = TRUE;
3561                 StopClocks();
3562                 continue;
3563             }
3564
3565             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3566                 ics_clock_paused = FALSE;
3567                 StartClocks();
3568                 continue;
3569             }
3570
3571             /* Grab player ratings from the Creating: message.
3572                Note we have to check for the special case when
3573                the ICS inserts things like [white] or [black]. */
3574             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3575                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3576                 /* star_matches:
3577                    0    player 1 name (not necessarily white)
3578                    1    player 1 rating
3579                    2    empty, white, or black (IGNORED)
3580                    3    player 2 name (not necessarily black)
3581                    4    player 2 rating
3582
3583                    The names/ratings are sorted out when the game
3584                    actually starts (below).
3585                 */
3586                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3587                 player1Rating = string_to_rating(star_match[1]);
3588                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3589                 player2Rating = string_to_rating(star_match[4]);
3590
3591                 if (appData.debugMode)
3592                   fprintf(debugFP,
3593                           "Ratings from 'Creating:' %s %d, %s %d\n",
3594                           player1Name, player1Rating,
3595                           player2Name, player2Rating);
3596
3597                 continue;
3598             }
3599
3600             /* Improved generic start/end-of-game messages */
3601             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3602                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3603                 /* If tkind == 0: */
3604                 /* star_match[0] is the game number */
3605                 /*           [1] is the white player's name */
3606                 /*           [2] is the black player's name */
3607                 /* For end-of-game: */
3608                 /*           [3] is the reason for the game end */
3609                 /*           [4] is a PGN end game-token, preceded by " " */
3610                 /* For start-of-game: */
3611                 /*           [3] begins with "Creating" or "Continuing" */
3612                 /*           [4] is " *" or empty (don't care). */
3613                 int gamenum = atoi(star_match[0]);
3614                 char *whitename, *blackname, *why, *endtoken;
3615                 ChessMove endtype = EndOfFile;
3616
3617                 if (tkind == 0) {
3618                   whitename = star_match[1];
3619                   blackname = star_match[2];
3620                   why = star_match[3];
3621                   endtoken = star_match[4];
3622                 } else {
3623                   whitename = star_match[1];
3624                   blackname = star_match[3];
3625                   why = star_match[5];
3626                   endtoken = star_match[6];
3627                 }
3628
3629                 /* Game start messages */
3630                 if (strncmp(why, "Creating ", 9) == 0 ||
3631                     strncmp(why, "Continuing ", 11) == 0) {
3632                     gs_gamenum = gamenum;
3633                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3634                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3635 #if ZIPPY
3636                     if (appData.zippyPlay) {
3637                         ZippyGameStart(whitename, blackname);
3638                     }
3639 #endif /*ZIPPY*/
3640                     partnerBoardValid = FALSE; // [HGM] bughouse
3641                     continue;
3642                 }
3643
3644                 /* Game end messages */
3645                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3646                     ics_gamenum != gamenum) {
3647                     continue;
3648                 }
3649                 while (endtoken[0] == ' ') endtoken++;
3650                 switch (endtoken[0]) {
3651                   case '*':
3652                   default:
3653                     endtype = GameUnfinished;
3654                     break;
3655                   case '0':
3656                     endtype = BlackWins;
3657                     break;
3658                   case '1':
3659                     if (endtoken[1] == '/')
3660                       endtype = GameIsDrawn;
3661                     else
3662                       endtype = WhiteWins;
3663                     break;
3664                 }
3665                 GameEnds(endtype, why, GE_ICS);
3666 #if ZIPPY
3667                 if (appData.zippyPlay && first.initDone) {
3668                     ZippyGameEnd(endtype, why);
3669                     if (first.pr == NULL) {
3670                       /* Start the next process early so that we'll
3671                          be ready for the next challenge */
3672                       StartChessProgram(&first);
3673                     }
3674                     /* Send "new" early, in case this command takes
3675                        a long time to finish, so that we'll be ready
3676                        for the next challenge. */
3677                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3678                     Reset(TRUE, TRUE);
3679                 }
3680 #endif /*ZIPPY*/
3681                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3682                 continue;
3683             }
3684
3685             if (looking_at(buf, &i, "Removing game * from observation") ||
3686                 looking_at(buf, &i, "no longer observing game *") ||
3687                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3688                 if (gameMode == IcsObserving &&
3689                     atoi(star_match[0]) == ics_gamenum)
3690                   {
3691                       /* icsEngineAnalyze */
3692                       if (appData.icsEngineAnalyze) {
3693                             ExitAnalyzeMode();
3694                             ModeHighlight();
3695                       }
3696                       StopClocks();
3697                       gameMode = IcsIdle;
3698                       ics_gamenum = -1;
3699                       ics_user_moved = FALSE;
3700                   }
3701                 continue;
3702             }
3703
3704             if (looking_at(buf, &i, "no longer examining game *")) {
3705                 if (gameMode == IcsExamining &&
3706                     atoi(star_match[0]) == ics_gamenum)
3707                   {
3708                       gameMode = IcsIdle;
3709                       ics_gamenum = -1;
3710                       ics_user_moved = FALSE;
3711                   }
3712                 continue;
3713             }
3714
3715             /* Advance leftover_start past any newlines we find,
3716                so only partial lines can get reparsed */
3717             if (looking_at(buf, &i, "\n")) {
3718                 prevColor = curColor;
3719                 if (curColor != ColorNormal) {
3720                     if (oldi > next_out) {
3721                         SendToPlayer(&buf[next_out], oldi - next_out);
3722                         next_out = oldi;
3723                     }
3724                     Colorize(ColorNormal, FALSE);
3725                     curColor = ColorNormal;
3726                 }
3727                 if (started == STARTED_BOARD) {
3728                     started = STARTED_NONE;
3729                     parse[parse_pos] = NULLCHAR;
3730                     ParseBoard12(parse);
3731                     ics_user_moved = 0;
3732
3733                     /* Send premove here */
3734                     if (appData.premove) {
3735                       char str[MSG_SIZ];
3736                       if (currentMove == 0 &&
3737                           gameMode == IcsPlayingWhite &&
3738                           appData.premoveWhite) {
3739                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3740                         if (appData.debugMode)
3741                           fprintf(debugFP, "Sending premove:\n");
3742                         SendToICS(str);
3743                       } else if (currentMove == 1 &&
3744                                  gameMode == IcsPlayingBlack &&
3745                                  appData.premoveBlack) {
3746                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3747                         if (appData.debugMode)
3748                           fprintf(debugFP, "Sending premove:\n");
3749                         SendToICS(str);
3750                       } else if (gotPremove) {
3751                         gotPremove = 0;
3752                         ClearPremoveHighlights();
3753                         if (appData.debugMode)
3754                           fprintf(debugFP, "Sending premove:\n");
3755                           UserMoveEvent(premoveFromX, premoveFromY,
3756                                         premoveToX, premoveToY,
3757                                         premovePromoChar);
3758                       }
3759                     }
3760
3761                     /* Usually suppress following prompt */
3762                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3763                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3764                         if (looking_at(buf, &i, "*% ")) {
3765                             savingComment = FALSE;
3766                             suppressKibitz = 0;
3767                         }
3768                     }
3769                     next_out = i;
3770                 } else if (started == STARTED_HOLDINGS) {
3771                     int gamenum;
3772                     char new_piece[MSG_SIZ];
3773                     started = STARTED_NONE;
3774                     parse[parse_pos] = NULLCHAR;
3775                     if (appData.debugMode)
3776                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3777                                                         parse, currentMove);
3778                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3779                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3780                         if (gameInfo.variant == VariantNormal) {
3781                           /* [HGM] We seem to switch variant during a game!
3782                            * Presumably no holdings were displayed, so we have
3783                            * to move the position two files to the right to
3784                            * create room for them!
3785                            */
3786                           VariantClass newVariant;
3787                           switch(gameInfo.boardWidth) { // base guess on board width
3788                                 case 9:  newVariant = VariantShogi; break;
3789                                 case 10: newVariant = VariantGreat; break;
3790                                 default: newVariant = VariantCrazyhouse; break;
3791                           }
3792                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3793                           /* Get a move list just to see the header, which
3794                              will tell us whether this is really bug or zh */
3795                           if (ics_getting_history == H_FALSE) {
3796                             ics_getting_history = H_REQUESTED;
3797                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3798                             SendToICS(str);
3799                           }
3800                         }
3801                         new_piece[0] = NULLCHAR;
3802                         sscanf(parse, "game %d white [%s black [%s <- %s",
3803                                &gamenum, white_holding, black_holding,
3804                                new_piece);
3805                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3806                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3807                         /* [HGM] copy holdings to board holdings area */
3808                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3809                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3810                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3811 #if ZIPPY
3812                         if (appData.zippyPlay && first.initDone) {
3813                             ZippyHoldings(white_holding, black_holding,
3814                                           new_piece);
3815                         }
3816 #endif /*ZIPPY*/
3817                         if (tinyLayout || smallLayout) {
3818                             char wh[16], bh[16];
3819                             PackHolding(wh, white_holding);
3820                             PackHolding(bh, black_holding);
3821                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3822                                     gameInfo.white, gameInfo.black);
3823                         } else {
3824                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3825                                     gameInfo.white, white_holding,
3826                                     gameInfo.black, black_holding);
3827                         }
3828                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3829                         DrawPosition(FALSE, boards[currentMove]);
3830                         DisplayTitle(str);
3831                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3832                         sscanf(parse, "game %d white [%s black [%s <- %s",
3833                                &gamenum, white_holding, black_holding,
3834                                new_piece);
3835                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3836                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3837                         /* [HGM] copy holdings to partner-board holdings area */
3838                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3839                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3840                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3841                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3842                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3843                       }
3844                     }
3845                     /* Suppress following prompt */
3846                     if (looking_at(buf, &i, "*% ")) {
3847                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3848                         savingComment = FALSE;
3849                         suppressKibitz = 0;
3850                     }
3851                     next_out = i;
3852                 }
3853                 continue;
3854             }
3855
3856             i++;                /* skip unparsed character and loop back */
3857         }
3858
3859         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3860 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3861 //          SendToPlayer(&buf[next_out], i - next_out);
3862             started != STARTED_HOLDINGS && leftover_start > next_out) {
3863             SendToPlayer(&buf[next_out], leftover_start - next_out);
3864             next_out = i;
3865         }
3866
3867         leftover_len = buf_len - leftover_start;
3868         /* if buffer ends with something we couldn't parse,
3869            reparse it after appending the next read */
3870
3871     } else if (count == 0) {
3872         RemoveInputSource(isr);
3873         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3874     } else {
3875         DisplayFatalError(_("Error reading from ICS"), error, 1);
3876     }
3877 }
3878
3879
3880 /* Board style 12 looks like this:
3881
3882    <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
3883
3884  * The "<12> " is stripped before it gets to this routine.  The two
3885  * trailing 0's (flip state and clock ticking) are later addition, and
3886  * some chess servers may not have them, or may have only the first.
3887  * Additional trailing fields may be added in the future.
3888  */
3889
3890 #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"
3891
3892 #define RELATION_OBSERVING_PLAYED    0
3893 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3894 #define RELATION_PLAYING_MYMOVE      1
3895 #define RELATION_PLAYING_NOTMYMOVE  -1
3896 #define RELATION_EXAMINING           2
3897 #define RELATION_ISOLATED_BOARD     -3
3898 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3899
3900 void
3901 ParseBoard12(string)
3902      char *string;
3903 {
3904     GameMode newGameMode;
3905     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3906     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3907     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3908     char to_play, board_chars[200];
3909     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3910     char black[32], white[32];
3911     Board board;
3912     int prevMove = currentMove;
3913     int ticking = 2;
3914     ChessMove moveType;
3915     int fromX, fromY, toX, toY;
3916     char promoChar;
3917     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3918     char *bookHit = NULL; // [HGM] book
3919     Boolean weird = FALSE, reqFlag = FALSE;
3920
3921     fromX = fromY = toX = toY = -1;
3922
3923     newGame = FALSE;
3924
3925     if (appData.debugMode)
3926       fprintf(debugFP, _("Parsing board: %s\n"), string);
3927
3928     move_str[0] = NULLCHAR;
3929     elapsed_time[0] = NULLCHAR;
3930     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3931         int  i = 0, j;
3932         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3933             if(string[i] == ' ') { ranks++; files = 0; }
3934             else files++;
3935             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3936             i++;
3937         }
3938         for(j = 0; j <i; j++) board_chars[j] = string[j];
3939         board_chars[i] = '\0';
3940         string += i + 1;
3941     }
3942     n = sscanf(string, PATTERN, &to_play, &double_push,
3943                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3944                &gamenum, white, black, &relation, &basetime, &increment,
3945                &white_stren, &black_stren, &white_time, &black_time,
3946                &moveNum, str, elapsed_time, move_str, &ics_flip,
3947                &ticking);
3948
3949     if (n < 21) {
3950         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3951         DisplayError(str, 0);
3952         return;
3953     }
3954
3955     /* Convert the move number to internal form */
3956     moveNum = (moveNum - 1) * 2;
3957     if (to_play == 'B') moveNum++;
3958     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3959       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3960                         0, 1);
3961       return;
3962     }
3963
3964     switch (relation) {
3965       case RELATION_OBSERVING_PLAYED:
3966       case RELATION_OBSERVING_STATIC:
3967         if (gamenum == -1) {
3968             /* Old ICC buglet */
3969             relation = RELATION_OBSERVING_STATIC;
3970         }
3971         newGameMode = IcsObserving;
3972         break;
3973       case RELATION_PLAYING_MYMOVE:
3974       case RELATION_PLAYING_NOTMYMOVE:
3975         newGameMode =
3976           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3977             IcsPlayingWhite : IcsPlayingBlack;
3978         break;
3979       case RELATION_EXAMINING:
3980         newGameMode = IcsExamining;
3981         break;
3982       case RELATION_ISOLATED_BOARD:
3983       default:
3984         /* Just display this board.  If user was doing something else,
3985            we will forget about it until the next board comes. */
3986         newGameMode = IcsIdle;
3987         break;
3988       case RELATION_STARTING_POSITION:
3989         newGameMode = gameMode;
3990         break;
3991     }
3992
3993     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3994          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3995       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3996       char *toSqr;
3997       for (k = 0; k < ranks; k++) {
3998         for (j = 0; j < files; j++)
3999           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4000         if(gameInfo.holdingsWidth > 1) {
4001              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4002              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4003         }
4004       }
4005       CopyBoard(partnerBoard, board);
4006       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4007         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4008         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4009       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4010       if(toSqr = strchr(str, '-')) {
4011         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4012         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4013       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4014       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4015       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4016       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4017       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4018       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4019                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4020       DisplayMessage(partnerStatus, "");
4021         partnerBoardValid = TRUE;
4022       return;
4023     }
4024
4025     /* Modify behavior for initial board display on move listing
4026        of wild games.
4027        */
4028     switch (ics_getting_history) {
4029       case H_FALSE:
4030       case H_REQUESTED:
4031         break;
4032       case H_GOT_REQ_HEADER:
4033       case H_GOT_UNREQ_HEADER:
4034         /* This is the initial position of the current game */
4035         gamenum = ics_gamenum;
4036         moveNum = 0;            /* old ICS bug workaround */
4037         if (to_play == 'B') {
4038           startedFromSetupPosition = TRUE;
4039           blackPlaysFirst = TRUE;
4040           moveNum = 1;
4041           if (forwardMostMove == 0) forwardMostMove = 1;
4042           if (backwardMostMove == 0) backwardMostMove = 1;
4043           if (currentMove == 0) currentMove = 1;
4044         }
4045         newGameMode = gameMode;
4046         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4047         break;
4048       case H_GOT_UNWANTED_HEADER:
4049         /* This is an initial board that we don't want */
4050         return;
4051       case H_GETTING_MOVES:
4052         /* Should not happen */
4053         DisplayError(_("Error gathering move list: extra board"), 0);
4054         ics_getting_history = H_FALSE;
4055         return;
4056     }
4057
4058    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4059                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4060      /* [HGM] We seem to have switched variant unexpectedly
4061       * Try to guess new variant from board size
4062       */
4063           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4064           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4065           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4066           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4067           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4068           if(!weird) newVariant = VariantNormal;
4069           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4070           /* Get a move list just to see the header, which
4071              will tell us whether this is really bug or zh */
4072           if (ics_getting_history == H_FALSE) {
4073             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4074             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4075             SendToICS(str);
4076           }
4077     }
4078
4079     /* Take action if this is the first board of a new game, or of a
4080        different game than is currently being displayed.  */
4081     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4082         relation == RELATION_ISOLATED_BOARD) {
4083
4084         /* Forget the old game and get the history (if any) of the new one */
4085         if (gameMode != BeginningOfGame) {
4086           Reset(TRUE, TRUE);
4087         }
4088         newGame = TRUE;
4089         if (appData.autoRaiseBoard) BoardToTop();
4090         prevMove = -3;
4091         if (gamenum == -1) {
4092             newGameMode = IcsIdle;
4093         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4094                    appData.getMoveList && !reqFlag) {
4095             /* Need to get game history */
4096             ics_getting_history = H_REQUESTED;
4097             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4098             SendToICS(str);
4099         }
4100
4101         /* Initially flip the board to have black on the bottom if playing
4102            black or if the ICS flip flag is set, but let the user change
4103            it with the Flip View button. */
4104         flipView = appData.autoFlipView ?
4105           (newGameMode == IcsPlayingBlack) || ics_flip :
4106           appData.flipView;
4107
4108         /* Done with values from previous mode; copy in new ones */
4109         gameMode = newGameMode;
4110         ModeHighlight();
4111         ics_gamenum = gamenum;
4112         if (gamenum == gs_gamenum) {
4113             int klen = strlen(gs_kind);
4114             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4115             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4116             gameInfo.event = StrSave(str);
4117         } else {
4118             gameInfo.event = StrSave("ICS game");
4119         }
4120         gameInfo.site = StrSave(appData.icsHost);
4121         gameInfo.date = PGNDate();
4122         gameInfo.round = StrSave("-");
4123         gameInfo.white = StrSave(white);
4124         gameInfo.black = StrSave(black);
4125         timeControl = basetime * 60 * 1000;
4126         timeControl_2 = 0;
4127         timeIncrement = increment * 1000;
4128         movesPerSession = 0;
4129         gameInfo.timeControl = TimeControlTagValue();
4130         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4131   if (appData.debugMode) {
4132     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4133     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4134     setbuf(debugFP, NULL);
4135   }
4136
4137         gameInfo.outOfBook = NULL;
4138
4139         /* Do we have the ratings? */
4140         if (strcmp(player1Name, white) == 0 &&
4141             strcmp(player2Name, black) == 0) {
4142             if (appData.debugMode)
4143               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4144                       player1Rating, player2Rating);
4145             gameInfo.whiteRating = player1Rating;
4146             gameInfo.blackRating = player2Rating;
4147         } else if (strcmp(player2Name, white) == 0 &&
4148                    strcmp(player1Name, black) == 0) {
4149             if (appData.debugMode)
4150               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4151                       player2Rating, player1Rating);
4152             gameInfo.whiteRating = player2Rating;
4153             gameInfo.blackRating = player1Rating;
4154         }
4155         player1Name[0] = player2Name[0] = NULLCHAR;
4156
4157         /* Silence shouts if requested */
4158         if (appData.quietPlay &&
4159             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4160             SendToICS(ics_prefix);
4161             SendToICS("set shout 0\n");
4162         }
4163     }
4164
4165     /* Deal with midgame name changes */
4166     if (!newGame) {
4167         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4168             if (gameInfo.white) free(gameInfo.white);
4169             gameInfo.white = StrSave(white);
4170         }
4171         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4172             if (gameInfo.black) free(gameInfo.black);
4173             gameInfo.black = StrSave(black);
4174         }
4175     }
4176
4177     /* Throw away game result if anything actually changes in examine mode */
4178     if (gameMode == IcsExamining && !newGame) {
4179         gameInfo.result = GameUnfinished;
4180         if (gameInfo.resultDetails != NULL) {
4181             free(gameInfo.resultDetails);
4182             gameInfo.resultDetails = NULL;
4183         }
4184     }
4185
4186     /* In pausing && IcsExamining mode, we ignore boards coming
4187        in if they are in a different variation than we are. */
4188     if (pauseExamInvalid) return;
4189     if (pausing && gameMode == IcsExamining) {
4190         if (moveNum <= pauseExamForwardMostMove) {
4191             pauseExamInvalid = TRUE;
4192             forwardMostMove = pauseExamForwardMostMove;
4193             return;
4194         }
4195     }
4196
4197   if (appData.debugMode) {
4198     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4199   }
4200     /* Parse the board */
4201     for (k = 0; k < ranks; k++) {
4202       for (j = 0; j < files; j++)
4203         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4204       if(gameInfo.holdingsWidth > 1) {
4205            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4206            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4207       }
4208     }
4209     CopyBoard(boards[moveNum], board);
4210     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4211     if (moveNum == 0) {
4212         startedFromSetupPosition =
4213           !CompareBoards(board, initialPosition);
4214         if(startedFromSetupPosition)
4215             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4216     }
4217
4218     /* [HGM] Set castling rights. Take the outermost Rooks,
4219        to make it also work for FRC opening positions. Note that board12
4220        is really defective for later FRC positions, as it has no way to
4221        indicate which Rook can castle if they are on the same side of King.
4222        For the initial position we grant rights to the outermost Rooks,
4223        and remember thos rights, and we then copy them on positions
4224        later in an FRC game. This means WB might not recognize castlings with
4225        Rooks that have moved back to their original position as illegal,
4226        but in ICS mode that is not its job anyway.
4227     */
4228     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4229     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4230
4231         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4232             if(board[0][i] == WhiteRook) j = i;
4233         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4234         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4235             if(board[0][i] == WhiteRook) j = i;
4236         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4237         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4238             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4239         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4240         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4241             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4242         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4243
4244         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4245         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4246             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4247         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4248             if(board[BOARD_HEIGHT-1][k] == bKing)
4249                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4250         if(gameInfo.variant == VariantTwoKings) {
4251             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4252             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4253             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4254         }
4255     } else { int r;
4256         r = boards[moveNum][CASTLING][0] = initialRights[0];
4257         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4258         r = boards[moveNum][CASTLING][1] = initialRights[1];
4259         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4260         r = boards[moveNum][CASTLING][3] = initialRights[3];
4261         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4262         r = boards[moveNum][CASTLING][4] = initialRights[4];
4263         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4264         /* wildcastle kludge: always assume King has rights */
4265         r = boards[moveNum][CASTLING][2] = initialRights[2];
4266         r = boards[moveNum][CASTLING][5] = initialRights[5];
4267     }
4268     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4269     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4270
4271
4272     if (ics_getting_history == H_GOT_REQ_HEADER ||
4273         ics_getting_history == H_GOT_UNREQ_HEADER) {
4274         /* This was an initial position from a move list, not
4275            the current position */
4276         return;
4277     }
4278
4279     /* Update currentMove and known move number limits */
4280     newMove = newGame || moveNum > forwardMostMove;
4281
4282     if (newGame) {
4283         forwardMostMove = backwardMostMove = currentMove = moveNum;
4284         if (gameMode == IcsExamining && moveNum == 0) {
4285           /* Workaround for ICS limitation: we are not told the wild
4286              type when starting to examine a game.  But if we ask for
4287              the move list, the move list header will tell us */
4288             ics_getting_history = H_REQUESTED;
4289             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4290             SendToICS(str);
4291         }
4292     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4293                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4294 #if ZIPPY
4295         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4296         /* [HGM] applied this also to an engine that is silently watching        */
4297         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4298             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4299             gameInfo.variant == currentlyInitializedVariant) {
4300           takeback = forwardMostMove - moveNum;
4301           for (i = 0; i < takeback; i++) {
4302             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4303             SendToProgram("undo\n", &first);
4304           }
4305         }
4306 #endif
4307
4308         forwardMostMove = moveNum;
4309         if (!pausing || currentMove > forwardMostMove)
4310           currentMove = forwardMostMove;
4311     } else {
4312         /* New part of history that is not contiguous with old part */
4313         if (pausing && gameMode == IcsExamining) {
4314             pauseExamInvalid = TRUE;
4315             forwardMostMove = pauseExamForwardMostMove;
4316             return;
4317         }
4318         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4319 #if ZIPPY
4320             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4321                 // [HGM] when we will receive the move list we now request, it will be
4322                 // fed to the engine from the first move on. So if the engine is not
4323                 // in the initial position now, bring it there.
4324                 InitChessProgram(&first, 0);
4325             }
4326 #endif
4327             ics_getting_history = H_REQUESTED;
4328             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4329             SendToICS(str);
4330         }
4331         forwardMostMove = backwardMostMove = currentMove = moveNum;
4332     }
4333
4334     /* Update the clocks */
4335     if (strchr(elapsed_time, '.')) {
4336       /* Time is in ms */
4337       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4338       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4339     } else {
4340       /* Time is in seconds */
4341       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4342       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4343     }
4344
4345
4346 #if ZIPPY
4347     if (appData.zippyPlay && newGame &&
4348         gameMode != IcsObserving && gameMode != IcsIdle &&
4349         gameMode != IcsExamining)
4350       ZippyFirstBoard(moveNum, basetime, increment);
4351 #endif
4352
4353     /* Put the move on the move list, first converting
4354        to canonical algebraic form. */
4355     if (moveNum > 0) {
4356   if (appData.debugMode) {
4357     if (appData.debugMode) { int f = forwardMostMove;
4358         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4359                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4360                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4361     }
4362     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4363     fprintf(debugFP, "moveNum = %d\n", moveNum);
4364     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4365     setbuf(debugFP, NULL);
4366   }
4367         if (moveNum <= backwardMostMove) {
4368             /* We don't know what the board looked like before
4369                this move.  Punt. */
4370           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4371             strcat(parseList[moveNum - 1], " ");
4372             strcat(parseList[moveNum - 1], elapsed_time);
4373             moveList[moveNum - 1][0] = NULLCHAR;
4374         } else if (strcmp(move_str, "none") == 0) {
4375             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4376             /* Again, we don't know what the board looked like;
4377                this is really the start of the game. */
4378             parseList[moveNum - 1][0] = NULLCHAR;
4379             moveList[moveNum - 1][0] = NULLCHAR;
4380             backwardMostMove = moveNum;
4381             startedFromSetupPosition = TRUE;
4382             fromX = fromY = toX = toY = -1;
4383         } else {
4384           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4385           //                 So we parse the long-algebraic move string in stead of the SAN move
4386           int valid; char buf[MSG_SIZ], *prom;
4387
4388           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4389                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4390           // str looks something like "Q/a1-a2"; kill the slash
4391           if(str[1] == '/')
4392             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4393           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4394           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4395                 strcat(buf, prom); // long move lacks promo specification!
4396           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4397                 if(appData.debugMode)
4398                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4399                 safeStrCpy(move_str, buf, MSG_SIZ);
4400           }
4401           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4402                                 &fromX, &fromY, &toX, &toY, &promoChar)
4403                || ParseOneMove(buf, moveNum - 1, &moveType,
4404                                 &fromX, &fromY, &toX, &toY, &promoChar);
4405           // end of long SAN patch
4406           if (valid) {
4407             (void) CoordsToAlgebraic(boards[moveNum - 1],
4408                                      PosFlags(moveNum - 1),
4409                                      fromY, fromX, toY, toX, promoChar,
4410                                      parseList[moveNum-1]);
4411             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4412               case MT_NONE:
4413               case MT_STALEMATE:
4414               default:
4415                 break;
4416               case MT_CHECK:
4417                 if(gameInfo.variant != VariantShogi)
4418                     strcat(parseList[moveNum - 1], "+");
4419                 break;
4420               case MT_CHECKMATE:
4421               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4422                 strcat(parseList[moveNum - 1], "#");
4423                 break;
4424             }
4425             strcat(parseList[moveNum - 1], " ");
4426             strcat(parseList[moveNum - 1], elapsed_time);
4427             /* currentMoveString is set as a side-effect of ParseOneMove */
4428             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4429             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4430             strcat(moveList[moveNum - 1], "\n");
4431
4432             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4433                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4434               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4435                 ChessSquare old, new = boards[moveNum][k][j];
4436                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4437                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4438                   if(old == new) continue;
4439                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4440                   else if(new == WhiteWazir || new == BlackWazir) {
4441                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4442                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4443                       else boards[moveNum][k][j] = old; // preserve type of Gold
4444                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4445                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4446               }
4447           } else {
4448             /* Move from ICS was illegal!?  Punt. */
4449             if (appData.debugMode) {
4450               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4451               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4452             }
4453             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4454             strcat(parseList[moveNum - 1], " ");
4455             strcat(parseList[moveNum - 1], elapsed_time);
4456             moveList[moveNum - 1][0] = NULLCHAR;
4457             fromX = fromY = toX = toY = -1;
4458           }
4459         }
4460   if (appData.debugMode) {
4461     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4462     setbuf(debugFP, NULL);
4463   }
4464
4465 #if ZIPPY
4466         /* Send move to chess program (BEFORE animating it). */
4467         if (appData.zippyPlay && !newGame && newMove &&
4468            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4469
4470             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4471                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4472                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4473                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4474                             move_str);
4475                     DisplayError(str, 0);
4476                 } else {
4477                     if (first.sendTime) {
4478                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4479                     }
4480                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4481                     if (firstMove && !bookHit) {
4482                         firstMove = FALSE;
4483                         if (first.useColors) {
4484                           SendToProgram(gameMode == IcsPlayingWhite ?
4485                                         "white\ngo\n" :
4486                                         "black\ngo\n", &first);
4487                         } else {
4488                           SendToProgram("go\n", &first);
4489                         }
4490                         first.maybeThinking = TRUE;
4491                     }
4492                 }
4493             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4494               if (moveList[moveNum - 1][0] == NULLCHAR) {
4495                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4496                 DisplayError(str, 0);
4497               } else {
4498                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4499                 SendMoveToProgram(moveNum - 1, &first);
4500               }
4501             }
4502         }
4503 #endif
4504     }
4505
4506     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4507         /* If move comes from a remote source, animate it.  If it
4508            isn't remote, it will have already been animated. */
4509         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4510             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4511         }
4512         if (!pausing && appData.highlightLastMove) {
4513             SetHighlights(fromX, fromY, toX, toY);
4514         }
4515     }
4516
4517     /* Start the clocks */
4518     whiteFlag = blackFlag = FALSE;
4519     appData.clockMode = !(basetime == 0 && increment == 0);
4520     if (ticking == 0) {
4521       ics_clock_paused = TRUE;
4522       StopClocks();
4523     } else if (ticking == 1) {
4524       ics_clock_paused = FALSE;
4525     }
4526     if (gameMode == IcsIdle ||
4527         relation == RELATION_OBSERVING_STATIC ||
4528         relation == RELATION_EXAMINING ||
4529         ics_clock_paused)
4530       DisplayBothClocks();
4531     else
4532       StartClocks();
4533
4534     /* Display opponents and material strengths */
4535     if (gameInfo.variant != VariantBughouse &&
4536         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4537         if (tinyLayout || smallLayout) {
4538             if(gameInfo.variant == VariantNormal)
4539               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4540                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4541                     basetime, increment);
4542             else
4543               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4544                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4545                     basetime, increment, (int) gameInfo.variant);
4546         } else {
4547             if(gameInfo.variant == VariantNormal)
4548               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4549                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4550                     basetime, increment);
4551             else
4552               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4553                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4554                     basetime, increment, VariantName(gameInfo.variant));
4555         }
4556         DisplayTitle(str);
4557   if (appData.debugMode) {
4558     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4559   }
4560     }
4561
4562
4563     /* Display the board */
4564     if (!pausing && !appData.noGUI) {
4565
4566       if (appData.premove)
4567           if (!gotPremove ||
4568              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4569              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4570               ClearPremoveHighlights();
4571
4572       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4573         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4574       DrawPosition(j, boards[currentMove]);
4575
4576       DisplayMove(moveNum - 1);
4577       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4578             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4579               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4580         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4581       }
4582     }
4583
4584     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4585 #if ZIPPY
4586     if(bookHit) { // [HGM] book: simulate book reply
4587         static char bookMove[MSG_SIZ]; // a bit generous?
4588
4589         programStats.nodes = programStats.depth = programStats.time =
4590         programStats.score = programStats.got_only_move = 0;
4591         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4592
4593         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4594         strcat(bookMove, bookHit);
4595         HandleMachineMove(bookMove, &first);
4596     }
4597 #endif
4598 }
4599
4600 void
4601 GetMoveListEvent()
4602 {
4603     char buf[MSG_SIZ];
4604     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4605         ics_getting_history = H_REQUESTED;
4606         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4607         SendToICS(buf);
4608     }
4609 }
4610
4611 void
4612 AnalysisPeriodicEvent(force)
4613      int force;
4614 {
4615     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4616          && !force) || !appData.periodicUpdates)
4617       return;
4618
4619     /* Send . command to Crafty to collect stats */
4620     SendToProgram(".\n", &first);
4621
4622     /* Don't send another until we get a response (this makes
4623        us stop sending to old Crafty's which don't understand
4624        the "." command (sending illegal cmds resets node count & time,
4625        which looks bad)) */
4626     programStats.ok_to_send = 0;
4627 }
4628
4629 void ics_update_width(new_width)
4630         int new_width;
4631 {
4632         ics_printf("set width %d\n", new_width);
4633 }
4634
4635 void
4636 SendMoveToProgram(moveNum, cps)
4637      int moveNum;
4638      ChessProgramState *cps;
4639 {
4640     char buf[MSG_SIZ];
4641
4642     if (cps->useUsermove) {
4643       SendToProgram("usermove ", cps);
4644     }
4645     if (cps->useSAN) {
4646       char *space;
4647       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4648         int len = space - parseList[moveNum];
4649         memcpy(buf, parseList[moveNum], len);
4650         buf[len++] = '\n';
4651         buf[len] = NULLCHAR;
4652       } else {
4653         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4654       }
4655       SendToProgram(buf, cps);
4656     } else {
4657       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4658         AlphaRank(moveList[moveNum], 4);
4659         SendToProgram(moveList[moveNum], cps);
4660         AlphaRank(moveList[moveNum], 4); // and back
4661       } else
4662       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4663        * the engine. It would be nice to have a better way to identify castle
4664        * moves here. */
4665       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4666                                                                          && cps->useOOCastle) {
4667         int fromX = moveList[moveNum][0] - AAA;
4668         int fromY = moveList[moveNum][1] - ONE;
4669         int toX = moveList[moveNum][2] - AAA;
4670         int toY = moveList[moveNum][3] - ONE;
4671         if((boards[moveNum][fromY][fromX] == WhiteKing
4672             && boards[moveNum][toY][toX] == WhiteRook)
4673            || (boards[moveNum][fromY][fromX] == BlackKing
4674                && boards[moveNum][toY][toX] == BlackRook)) {
4675           if(toX > fromX) SendToProgram("O-O\n", cps);
4676           else SendToProgram("O-O-O\n", cps);
4677         }
4678         else SendToProgram(moveList[moveNum], cps);
4679       }
4680       else SendToProgram(moveList[moveNum], cps);
4681       /* End of additions by Tord */
4682     }
4683
4684     /* [HGM] setting up the opening has brought engine in force mode! */
4685     /*       Send 'go' if we are in a mode where machine should play. */
4686     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4687         (gameMode == TwoMachinesPlay   ||
4688 #if ZIPPY
4689          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4690 #endif
4691          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4692         SendToProgram("go\n", cps);
4693   if (appData.debugMode) {
4694     fprintf(debugFP, "(extra)\n");
4695   }
4696     }
4697     setboardSpoiledMachineBlack = 0;
4698 }
4699
4700 void
4701 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4702      ChessMove moveType;
4703      int fromX, fromY, toX, toY;
4704      char promoChar;
4705 {
4706     char user_move[MSG_SIZ];
4707
4708     switch (moveType) {
4709       default:
4710         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4711                 (int)moveType, fromX, fromY, toX, toY);
4712         DisplayError(user_move + strlen("say "), 0);
4713         break;
4714       case WhiteKingSideCastle:
4715       case BlackKingSideCastle:
4716       case WhiteQueenSideCastleWild:
4717       case BlackQueenSideCastleWild:
4718       /* PUSH Fabien */
4719       case WhiteHSideCastleFR:
4720       case BlackHSideCastleFR:
4721       /* POP Fabien */
4722         snprintf(user_move, MSG_SIZ, "o-o\n");
4723         break;
4724       case WhiteQueenSideCastle:
4725       case BlackQueenSideCastle:
4726       case WhiteKingSideCastleWild:
4727       case BlackKingSideCastleWild:
4728       /* PUSH Fabien */
4729       case WhiteASideCastleFR:
4730       case BlackASideCastleFR:
4731       /* POP Fabien */
4732         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4733         break;
4734       case WhiteNonPromotion:
4735       case BlackNonPromotion:
4736         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4737         break;
4738       case WhitePromotion:
4739       case BlackPromotion:
4740         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4741           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4742                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4743                 PieceToChar(WhiteFerz));
4744         else if(gameInfo.variant == VariantGreat)
4745           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4746                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4747                 PieceToChar(WhiteMan));
4748         else
4749           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4750                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4751                 promoChar);
4752         break;
4753       case WhiteDrop:
4754       case BlackDrop:
4755       drop:
4756         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4757                  ToUpper(PieceToChar((ChessSquare) fromX)),
4758                  AAA + toX, ONE + toY);
4759         break;
4760       case IllegalMove:  /* could be a variant we don't quite understand */
4761         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4762       case NormalMove:
4763       case WhiteCapturesEnPassant:
4764       case BlackCapturesEnPassant:
4765         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4766                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4767         break;
4768     }
4769     SendToICS(user_move);
4770     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4771         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4772 }
4773
4774 void
4775 UploadGameEvent()
4776 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4777     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4778     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4779     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4780         DisplayError("You cannot do this while you are playing or observing", 0);
4781         return;
4782     }
4783     if(gameMode != IcsExamining) { // is this ever not the case?
4784         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4785
4786         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4787           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4788         } else { // on FICS we must first go to general examine mode
4789           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4790         }
4791         if(gameInfo.variant != VariantNormal) {
4792             // try figure out wild number, as xboard names are not always valid on ICS
4793             for(i=1; i<=36; i++) {
4794               snprintf(buf, MSG_SIZ, "wild/%d", i);
4795                 if(StringToVariant(buf) == gameInfo.variant) break;
4796             }
4797             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4798             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4799             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4800         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4801         SendToICS(ics_prefix);
4802         SendToICS(buf);
4803         if(startedFromSetupPosition || backwardMostMove != 0) {
4804           fen = PositionToFEN(backwardMostMove, NULL);
4805           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4806             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4807             SendToICS(buf);
4808           } else { // FICS: everything has to set by separate bsetup commands
4809             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4810             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4811             SendToICS(buf);
4812             if(!WhiteOnMove(backwardMostMove)) {
4813                 SendToICS("bsetup tomove black\n");
4814             }
4815             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4816             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4817             SendToICS(buf);
4818             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4819             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4820             SendToICS(buf);
4821             i = boards[backwardMostMove][EP_STATUS];
4822             if(i >= 0) { // set e.p.
4823               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4824                 SendToICS(buf);
4825             }
4826             bsetup++;
4827           }
4828         }
4829       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4830             SendToICS("bsetup done\n"); // switch to normal examining.
4831     }
4832     for(i = backwardMostMove; i<last; i++) {
4833         char buf[20];
4834         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4835         SendToICS(buf);
4836     }
4837     SendToICS(ics_prefix);
4838     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4839 }
4840
4841 void
4842 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4843      int rf, ff, rt, ft;
4844      char promoChar;
4845      char move[7];
4846 {
4847     if (rf == DROP_RANK) {
4848       sprintf(move, "%c@%c%c\n",
4849                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4850     } else {
4851         if (promoChar == 'x' || promoChar == NULLCHAR) {
4852           sprintf(move, "%c%c%c%c\n",
4853                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4854         } else {
4855             sprintf(move, "%c%c%c%c%c\n",
4856                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4857         }
4858     }
4859 }
4860
4861 void
4862 ProcessICSInitScript(f)
4863      FILE *f;
4864 {
4865     char buf[MSG_SIZ];
4866
4867     while (fgets(buf, MSG_SIZ, f)) {
4868         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4869     }
4870
4871     fclose(f);
4872 }
4873
4874
4875 static int lastX, lastY, selectFlag, dragging;
4876
4877 void
4878 Sweep(int step)
4879 {
4880     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4881     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4882     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4883     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4884     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4885     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4886     do {
4887         promoSweep -= step;
4888         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4889         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4890         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4891         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4892         if(!step) step = 1;
4893     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4894             appData.testLegality && (promoSweep == king ||
4895             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4896     ChangeDragPiece(promoSweep);
4897 }
4898
4899 int PromoScroll(int x, int y)
4900 {
4901   int step = 0;
4902
4903   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4904   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4905   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4906   if(!step) return FALSE;
4907   lastX = x; lastY = y;
4908   if((promoSweep < BlackPawn) == flipView) step = -step;
4909   if(step > 0) selectFlag = 1;
4910   if(!selectFlag) Sweep(step);
4911   return FALSE;
4912 }
4913
4914 void
4915 NextPiece(int step)
4916 {
4917     ChessSquare piece = boards[currentMove][toY][toX];
4918     do {
4919         pieceSweep -= step;
4920         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4921         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4922         if(!step) step = -1;
4923     } while(PieceToChar(pieceSweep) == '.');
4924     boards[currentMove][toY][toX] = pieceSweep;
4925     DrawPosition(FALSE, boards[currentMove]);
4926     boards[currentMove][toY][toX] = piece;
4927 }
4928 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4929 void
4930 AlphaRank(char *move, int n)
4931 {
4932 //    char *p = move, c; int x, y;
4933
4934     if (appData.debugMode) {
4935         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4936     }
4937
4938     if(move[1]=='*' &&
4939        move[2]>='0' && move[2]<='9' &&
4940        move[3]>='a' && move[3]<='x'    ) {
4941         move[1] = '@';
4942         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4943         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4944     } else
4945     if(move[0]>='0' && move[0]<='9' &&
4946        move[1]>='a' && move[1]<='x' &&
4947        move[2]>='0' && move[2]<='9' &&
4948        move[3]>='a' && move[3]<='x'    ) {
4949         /* input move, Shogi -> normal */
4950         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4951         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4952         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4953         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4954     } else
4955     if(move[1]=='@' &&
4956        move[3]>='0' && move[3]<='9' &&
4957        move[2]>='a' && move[2]<='x'    ) {
4958         move[1] = '*';
4959         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4960         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4961     } else
4962     if(
4963        move[0]>='a' && move[0]<='x' &&
4964        move[3]>='0' && move[3]<='9' &&
4965        move[2]>='a' && move[2]<='x'    ) {
4966          /* output move, normal -> Shogi */
4967         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4968         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4969         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4970         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4971         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4972     }
4973     if (appData.debugMode) {
4974         fprintf(debugFP, "   out = '%s'\n", move);
4975     }
4976 }
4977
4978 char yy_textstr[8000];
4979
4980 /* Parser for moves from gnuchess, ICS, or user typein box */
4981 Boolean
4982 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4983      char *move;
4984      int moveNum;
4985      ChessMove *moveType;
4986      int *fromX, *fromY, *toX, *toY;
4987      char *promoChar;
4988 {
4989     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4990
4991     switch (*moveType) {
4992       case WhitePromotion:
4993       case BlackPromotion:
4994       case WhiteNonPromotion:
4995       case BlackNonPromotion:
4996       case NormalMove:
4997       case WhiteCapturesEnPassant:
4998       case BlackCapturesEnPassant:
4999       case WhiteKingSideCastle:
5000       case WhiteQueenSideCastle:
5001       case BlackKingSideCastle:
5002       case BlackQueenSideCastle:
5003       case WhiteKingSideCastleWild:
5004       case WhiteQueenSideCastleWild:
5005       case BlackKingSideCastleWild:
5006       case BlackQueenSideCastleWild:
5007       /* Code added by Tord: */
5008       case WhiteHSideCastleFR:
5009       case WhiteASideCastleFR:
5010       case BlackHSideCastleFR:
5011       case BlackASideCastleFR:
5012       /* End of code added by Tord */
5013       case IllegalMove:         /* bug or odd chess variant */
5014         *fromX = currentMoveString[0] - AAA;
5015         *fromY = currentMoveString[1] - ONE;
5016         *toX = currentMoveString[2] - AAA;
5017         *toY = currentMoveString[3] - ONE;
5018         *promoChar = currentMoveString[4];
5019         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5020             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5021     if (appData.debugMode) {
5022         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5023     }
5024             *fromX = *fromY = *toX = *toY = 0;
5025             return FALSE;
5026         }
5027         if (appData.testLegality) {
5028           return (*moveType != IllegalMove);
5029         } else {
5030           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5031                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5032         }
5033
5034       case WhiteDrop:
5035       case BlackDrop:
5036         *fromX = *moveType == WhiteDrop ?
5037           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5038           (int) CharToPiece(ToLower(currentMoveString[0]));
5039         *fromY = DROP_RANK;
5040         *toX = currentMoveString[2] - AAA;
5041         *toY = currentMoveString[3] - ONE;
5042         *promoChar = NULLCHAR;
5043         return TRUE;
5044
5045       case AmbiguousMove:
5046       case ImpossibleMove:
5047       case EndOfFile:
5048       case ElapsedTime:
5049       case Comment:
5050       case PGNTag:
5051       case NAG:
5052       case WhiteWins:
5053       case BlackWins:
5054       case GameIsDrawn:
5055       default:
5056     if (appData.debugMode) {
5057         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5058     }
5059         /* bug? */
5060         *fromX = *fromY = *toX = *toY = 0;
5061         *promoChar = NULLCHAR;
5062         return FALSE;
5063     }
5064 }
5065
5066
5067 void
5068 ParsePV(char *pv, Boolean storeComments)
5069 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5070   int fromX, fromY, toX, toY; char promoChar;
5071   ChessMove moveType;
5072   Boolean valid;
5073   int nr = 0;
5074
5075   endPV = forwardMostMove;
5076   do {
5077     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5078     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5079     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5080 if(appData.debugMode){
5081 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);
5082 }
5083     if(!valid && nr == 0 &&
5084        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5085         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5086         // Hande case where played move is different from leading PV move
5087         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5088         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5089         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5090         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5091           endPV += 2; // if position different, keep this
5092           moveList[endPV-1][0] = fromX + AAA;
5093           moveList[endPV-1][1] = fromY + ONE;
5094           moveList[endPV-1][2] = toX + AAA;
5095           moveList[endPV-1][3] = toY + ONE;
5096           parseList[endPV-1][0] = NULLCHAR;
5097           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5098         }
5099       }
5100     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5101     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5102     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5103     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5104         valid++; // allow comments in PV
5105         continue;
5106     }
5107     nr++;
5108     if(endPV+1 > framePtr) break; // no space, truncate
5109     if(!valid) break;
5110     endPV++;
5111     CopyBoard(boards[endPV], boards[endPV-1]);
5112     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5113     moveList[endPV-1][0] = fromX + AAA;
5114     moveList[endPV-1][1] = fromY + ONE;
5115     moveList[endPV-1][2] = toX + AAA;
5116     moveList[endPV-1][3] = toY + ONE;
5117     moveList[endPV-1][4] = promoChar;
5118     moveList[endPV-1][5] = NULLCHAR;
5119     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5120     if(storeComments)
5121         CoordsToAlgebraic(boards[endPV - 1],
5122                              PosFlags(endPV - 1),
5123                              fromY, fromX, toY, toX, promoChar,
5124                              parseList[endPV - 1]);
5125     else
5126         parseList[endPV-1][0] = NULLCHAR;
5127   } while(valid);
5128   currentMove = endPV;
5129   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5130   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5131                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5132   DrawPosition(TRUE, boards[currentMove]);
5133 }
5134
5135 Boolean
5136 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5137 {
5138         int startPV;
5139         char *p;
5140
5141         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5142         lastX = x; lastY = y;
5143         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5144         startPV = index;
5145         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5146         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5147         index = startPV;
5148         do{ while(buf[index] && buf[index] != '\n') index++;
5149         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5150         buf[index] = 0;
5151         ParsePV(buf+startPV, FALSE);
5152         *start = startPV; *end = index-1;
5153         return TRUE;
5154 }
5155
5156 Boolean
5157 LoadPV(int x, int y)
5158 { // called on right mouse click to load PV
5159   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5160   lastX = x; lastY = y;
5161   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5162   return TRUE;
5163 }
5164
5165 void
5166 UnLoadPV()
5167 {
5168   if(endPV < 0) return;
5169   endPV = -1;
5170   currentMove = forwardMostMove;
5171   ClearPremoveHighlights();
5172   DrawPosition(TRUE, boards[currentMove]);
5173 }
5174
5175 void
5176 MovePV(int x, int y, int h)
5177 { // step through PV based on mouse coordinates (called on mouse move)
5178   int margin = h>>3, step = 0;
5179
5180   // we must somehow check if right button is still down (might be released off board!)
5181   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5182   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5183   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5184   if(!step) return;
5185   lastX = x; lastY = y;
5186
5187   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5188   if(endPV < 0) return;
5189   if(y < margin) step = 1; else
5190   if(y > h - margin) step = -1;
5191   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5192   currentMove += step;
5193   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5194   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5195                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5196   DrawPosition(FALSE, boards[currentMove]);
5197 }
5198
5199
5200 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5201 // All positions will have equal probability, but the current method will not provide a unique
5202 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5203 #define DARK 1
5204 #define LITE 2
5205 #define ANY 3
5206
5207 int squaresLeft[4];
5208 int piecesLeft[(int)BlackPawn];
5209 int seed, nrOfShuffles;
5210
5211 void GetPositionNumber()
5212 {       // sets global variable seed
5213         int i;
5214
5215         seed = appData.defaultFrcPosition;
5216         if(seed < 0) { // randomize based on time for negative FRC position numbers
5217                 for(i=0; i<50; i++) seed += random();
5218                 seed = random() ^ random() >> 8 ^ random() << 8;
5219                 if(seed<0) seed = -seed;
5220         }
5221 }
5222
5223 int put(Board board, int pieceType, int rank, int n, int shade)
5224 // put the piece on the (n-1)-th empty squares of the given shade
5225 {
5226         int i;
5227
5228         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5229                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5230                         board[rank][i] = (ChessSquare) pieceType;
5231                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5232                         squaresLeft[ANY]--;
5233                         piecesLeft[pieceType]--;
5234                         return i;
5235                 }
5236         }
5237         return -1;
5238 }
5239
5240
5241 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5242 // calculate where the next piece goes, (any empty square), and put it there
5243 {
5244         int i;
5245
5246         i = seed % squaresLeft[shade];
5247         nrOfShuffles *= squaresLeft[shade];
5248         seed /= squaresLeft[shade];
5249         put(board, pieceType, rank, i, shade);
5250 }
5251
5252 void AddTwoPieces(Board board, int pieceType, int rank)
5253 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5254 {
5255         int i, n=squaresLeft[ANY], j=n-1, k;
5256
5257         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5258         i = seed % k;  // pick one
5259         nrOfShuffles *= k;
5260         seed /= k;
5261         while(i >= j) i -= j--;
5262         j = n - 1 - j; i += j;
5263         put(board, pieceType, rank, j, ANY);
5264         put(board, pieceType, rank, i, ANY);
5265 }
5266
5267 void SetUpShuffle(Board board, int number)
5268 {
5269         int i, p, first=1;
5270
5271         GetPositionNumber(); nrOfShuffles = 1;
5272
5273         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5274         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5275         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5276
5277         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5278
5279         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5280             p = (int) board[0][i];
5281             if(p < (int) BlackPawn) piecesLeft[p] ++;
5282             board[0][i] = EmptySquare;
5283         }
5284
5285         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5286             // shuffles restricted to allow normal castling put KRR first
5287             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5288                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5289             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5290                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5291             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5292                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5293             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5294                 put(board, WhiteRook, 0, 0, ANY);
5295             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5296         }
5297
5298         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5299             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5300             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5301                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5302                 while(piecesLeft[p] >= 2) {
5303                     AddOnePiece(board, p, 0, LITE);
5304                     AddOnePiece(board, p, 0, DARK);
5305                 }
5306                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5307             }
5308
5309         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5310             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5311             // but we leave King and Rooks for last, to possibly obey FRC restriction
5312             if(p == (int)WhiteRook) continue;
5313             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5314             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5315         }
5316
5317         // now everything is placed, except perhaps King (Unicorn) and Rooks
5318
5319         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5320             // Last King gets castling rights
5321             while(piecesLeft[(int)WhiteUnicorn]) {
5322                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5323                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5324             }
5325
5326             while(piecesLeft[(int)WhiteKing]) {
5327                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5328                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5329             }
5330
5331
5332         } else {
5333             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5334             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5335         }
5336
5337         // Only Rooks can be left; simply place them all
5338         while(piecesLeft[(int)WhiteRook]) {
5339                 i = put(board, WhiteRook, 0, 0, ANY);
5340                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5341                         if(first) {
5342                                 first=0;
5343                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5344                         }
5345                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5346                 }
5347         }
5348         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5349             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5350         }
5351
5352         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5353 }
5354
5355 int SetCharTable( char *table, const char * map )
5356 /* [HGM] moved here from winboard.c because of its general usefulness */
5357 /*       Basically a safe strcpy that uses the last character as King */
5358 {
5359     int result = FALSE; int NrPieces;
5360
5361     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5362                     && NrPieces >= 12 && !(NrPieces&1)) {
5363         int i; /* [HGM] Accept even length from 12 to 34 */
5364
5365         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5366         for( i=0; i<NrPieces/2-1; i++ ) {
5367             table[i] = map[i];
5368             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5369         }
5370         table[(int) WhiteKing]  = map[NrPieces/2-1];
5371         table[(int) BlackKing]  = map[NrPieces-1];
5372
5373         result = TRUE;
5374     }
5375
5376     return result;
5377 }
5378
5379 void Prelude(Board board)
5380 {       // [HGM] superchess: random selection of exo-pieces
5381         int i, j, k; ChessSquare p;
5382         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5383
5384         GetPositionNumber(); // use FRC position number
5385
5386         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5387             SetCharTable(pieceToChar, appData.pieceToCharTable);
5388             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5389                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5390         }
5391
5392         j = seed%4;                 seed /= 4;
5393         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5394         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5395         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5396         j = seed%3 + (seed%3 >= j); seed /= 3;
5397         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5398         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5399         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5400         j = seed%3;                 seed /= 3;
5401         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5402         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5403         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5404         j = seed%2 + (seed%2 >= j); seed /= 2;
5405         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5406         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5407         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5408         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5409         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5410         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5411         put(board, exoPieces[0],    0, 0, ANY);
5412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5413 }
5414
5415 void
5416 InitPosition(redraw)
5417      int redraw;
5418 {
5419     ChessSquare (* pieces)[BOARD_FILES];
5420     int i, j, pawnRow, overrule,
5421     oldx = gameInfo.boardWidth,
5422     oldy = gameInfo.boardHeight,
5423     oldh = gameInfo.holdingsWidth;
5424     static int oldv;
5425
5426     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5427
5428     /* [AS] Initialize pv info list [HGM] and game status */
5429     {
5430         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5431             pvInfoList[i].depth = 0;
5432             boards[i][EP_STATUS] = EP_NONE;
5433             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5434         }
5435
5436         initialRulePlies = 0; /* 50-move counter start */
5437
5438         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5439         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5440     }
5441
5442
5443     /* [HGM] logic here is completely changed. In stead of full positions */
5444     /* the initialized data only consist of the two backranks. The switch */
5445     /* selects which one we will use, which is than copied to the Board   */
5446     /* initialPosition, which for the rest is initialized by Pawns and    */
5447     /* empty squares. This initial position is then copied to boards[0],  */
5448     /* possibly after shuffling, so that it remains available.            */
5449
5450     gameInfo.holdingsWidth = 0; /* default board sizes */
5451     gameInfo.boardWidth    = 8;
5452     gameInfo.boardHeight   = 8;
5453     gameInfo.holdingsSize  = 0;
5454     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5455     for(i=0; i<BOARD_FILES-2; i++)
5456       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5457     initialPosition[EP_STATUS] = EP_NONE;
5458     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5459     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5460          SetCharTable(pieceNickName, appData.pieceNickNames);
5461     else SetCharTable(pieceNickName, "............");
5462     pieces = FIDEArray;
5463
5464     switch (gameInfo.variant) {
5465     case VariantFischeRandom:
5466       shuffleOpenings = TRUE;
5467     default:
5468       break;
5469     case VariantShatranj:
5470       pieces = ShatranjArray;
5471       nrCastlingRights = 0;
5472       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5473       break;
5474     case VariantMakruk:
5475       pieces = makrukArray;
5476       nrCastlingRights = 0;
5477       startedFromSetupPosition = TRUE;
5478       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5479       break;
5480     case VariantTwoKings:
5481       pieces = twoKingsArray;
5482       break;
5483     case VariantCapaRandom:
5484       shuffleOpenings = TRUE;
5485     case VariantCapablanca:
5486       pieces = CapablancaArray;
5487       gameInfo.boardWidth = 10;
5488       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5489       break;
5490     case VariantGothic:
5491       pieces = GothicArray;
5492       gameInfo.boardWidth = 10;
5493       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5494       break;
5495     case VariantSChess:
5496       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5497       gameInfo.holdingsSize = 7;
5498       break;
5499     case VariantJanus:
5500       pieces = JanusArray;
5501       gameInfo.boardWidth = 10;
5502       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5503       nrCastlingRights = 6;
5504         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5505         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5506         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5507         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5508         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5509         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5510       break;
5511     case VariantFalcon:
5512       pieces = FalconArray;
5513       gameInfo.boardWidth = 10;
5514       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5515       break;
5516     case VariantXiangqi:
5517       pieces = XiangqiArray;
5518       gameInfo.boardWidth  = 9;
5519       gameInfo.boardHeight = 10;
5520       nrCastlingRights = 0;
5521       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5522       break;
5523     case VariantShogi:
5524       pieces = ShogiArray;
5525       gameInfo.boardWidth  = 9;
5526       gameInfo.boardHeight = 9;
5527       gameInfo.holdingsSize = 7;
5528       nrCastlingRights = 0;
5529       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5530       break;
5531     case VariantCourier:
5532       pieces = CourierArray;
5533       gameInfo.boardWidth  = 12;
5534       nrCastlingRights = 0;
5535       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5536       break;
5537     case VariantKnightmate:
5538       pieces = KnightmateArray;
5539       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5540       break;
5541     case VariantSpartan:
5542       pieces = SpartanArray;
5543       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5544       break;
5545     case VariantFairy:
5546       pieces = fairyArray;
5547       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5548       break;
5549     case VariantGreat:
5550       pieces = GreatArray;
5551       gameInfo.boardWidth = 10;
5552       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5553       gameInfo.holdingsSize = 8;
5554       break;
5555     case VariantSuper:
5556       pieces = FIDEArray;
5557       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5558       gameInfo.holdingsSize = 8;
5559       startedFromSetupPosition = TRUE;
5560       break;
5561     case VariantCrazyhouse:
5562     case VariantBughouse:
5563       pieces = FIDEArray;
5564       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5565       gameInfo.holdingsSize = 5;
5566       break;
5567     case VariantWildCastle:
5568       pieces = FIDEArray;
5569       /* !!?shuffle with kings guaranteed to be on d or e file */
5570       shuffleOpenings = 1;
5571       break;
5572     case VariantNoCastle:
5573       pieces = FIDEArray;
5574       nrCastlingRights = 0;
5575       /* !!?unconstrained back-rank shuffle */
5576       shuffleOpenings = 1;
5577       break;
5578     }
5579
5580     overrule = 0;
5581     if(appData.NrFiles >= 0) {
5582         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5583         gameInfo.boardWidth = appData.NrFiles;
5584     }
5585     if(appData.NrRanks >= 0) {
5586         gameInfo.boardHeight = appData.NrRanks;
5587     }
5588     if(appData.holdingsSize >= 0) {
5589         i = appData.holdingsSize;
5590         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5591         gameInfo.holdingsSize = i;
5592     }
5593     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5594     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5595         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5596
5597     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5598     if(pawnRow < 1) pawnRow = 1;
5599     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5600
5601     /* User pieceToChar list overrules defaults */
5602     if(appData.pieceToCharTable != NULL)
5603         SetCharTable(pieceToChar, appData.pieceToCharTable);
5604
5605     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5606
5607         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5608             s = (ChessSquare) 0; /* account holding counts in guard band */
5609         for( i=0; i<BOARD_HEIGHT; i++ )
5610             initialPosition[i][j] = s;
5611
5612         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5613         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5614         initialPosition[pawnRow][j] = WhitePawn;
5615         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5616         if(gameInfo.variant == VariantXiangqi) {
5617             if(j&1) {
5618                 initialPosition[pawnRow][j] =
5619                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5620                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5621                    initialPosition[2][j] = WhiteCannon;
5622                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5623                 }
5624             }
5625         }
5626         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5627     }
5628     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5629
5630             j=BOARD_LEFT+1;
5631             initialPosition[1][j] = WhiteBishop;
5632             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5633             j=BOARD_RGHT-2;
5634             initialPosition[1][j] = WhiteRook;
5635             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5636     }
5637
5638     if( nrCastlingRights == -1) {
5639         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5640         /*       This sets default castling rights from none to normal corners   */
5641         /* Variants with other castling rights must set them themselves above    */
5642         nrCastlingRights = 6;
5643
5644         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5645         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5646         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5647         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5648         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5649         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5650      }
5651
5652      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5653      if(gameInfo.variant == VariantGreat) { // promotion commoners
5654         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5655         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5656         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5657         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5658      }
5659      if( gameInfo.variant == VariantSChess ) {
5660       initialPosition[1][0] = BlackMarshall;
5661       initialPosition[2][0] = BlackAngel;
5662       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5663       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5664       initialPosition[1][1] = initialPosition[2][1] = 
5665       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5666      }
5667   if (appData.debugMode) {
5668     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5669   }
5670     if(shuffleOpenings) {
5671         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5672         startedFromSetupPosition = TRUE;
5673     }
5674     if(startedFromPositionFile) {
5675       /* [HGM] loadPos: use PositionFile for every new game */
5676       CopyBoard(initialPosition, filePosition);
5677       for(i=0; i<nrCastlingRights; i++)
5678           initialRights[i] = filePosition[CASTLING][i];
5679       startedFromSetupPosition = TRUE;
5680     }
5681
5682     CopyBoard(boards[0], initialPosition);
5683
5684     if(oldx != gameInfo.boardWidth ||
5685        oldy != gameInfo.boardHeight ||
5686        oldv != gameInfo.variant ||
5687        oldh != gameInfo.holdingsWidth
5688                                          )
5689             InitDrawingSizes(-2 ,0);
5690
5691     oldv = gameInfo.variant;
5692     if (redraw)
5693       DrawPosition(TRUE, boards[currentMove]);
5694 }
5695
5696 void
5697 SendBoard(cps, moveNum)
5698      ChessProgramState *cps;
5699      int moveNum;
5700 {
5701     char message[MSG_SIZ];
5702
5703     if (cps->useSetboard) {
5704       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5705       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5706       SendToProgram(message, cps);
5707       free(fen);
5708
5709     } else {
5710       ChessSquare *bp;
5711       int i, j;
5712       /* Kludge to set black to move, avoiding the troublesome and now
5713        * deprecated "black" command.
5714        */
5715       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5716         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5717
5718       SendToProgram("edit\n", cps);
5719       SendToProgram("#\n", cps);
5720       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5721         bp = &boards[moveNum][i][BOARD_LEFT];
5722         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5723           if ((int) *bp < (int) BlackPawn) {
5724             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5725                     AAA + j, ONE + i);
5726             if(message[0] == '+' || message[0] == '~') {
5727               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5728                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5729                         AAA + j, ONE + i);
5730             }
5731             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5732                 message[1] = BOARD_RGHT   - 1 - j + '1';
5733                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5734             }
5735             SendToProgram(message, cps);
5736           }
5737         }
5738       }
5739
5740       SendToProgram("c\n", cps);
5741       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5742         bp = &boards[moveNum][i][BOARD_LEFT];
5743         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5744           if (((int) *bp != (int) EmptySquare)
5745               && ((int) *bp >= (int) BlackPawn)) {
5746             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5747                     AAA + j, ONE + i);
5748             if(message[0] == '+' || message[0] == '~') {
5749               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5750                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5751                         AAA + j, ONE + i);
5752             }
5753             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5754                 message[1] = BOARD_RGHT   - 1 - j + '1';
5755                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5756             }
5757             SendToProgram(message, cps);
5758           }
5759         }
5760       }
5761
5762       SendToProgram(".\n", cps);
5763     }
5764     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5765 }
5766
5767 ChessSquare
5768 DefaultPromoChoice(int white)
5769 {
5770     ChessSquare result;
5771     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5772         result = WhiteFerz; // no choice
5773     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5774         result= WhiteKing; // in Suicide Q is the last thing we want
5775     else if(gameInfo.variant == VariantSpartan)
5776         result = white ? WhiteQueen : WhiteAngel;
5777     else result = WhiteQueen;
5778     if(!white) result = WHITE_TO_BLACK result;
5779     return result;
5780 }
5781
5782 static int autoQueen; // [HGM] oneclick
5783
5784 int
5785 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5786 {
5787     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5788     /* [HGM] add Shogi promotions */
5789     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5790     ChessSquare piece;
5791     ChessMove moveType;
5792     Boolean premove;
5793
5794     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5795     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5796
5797     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5798       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5799         return FALSE;
5800
5801     piece = boards[currentMove][fromY][fromX];
5802     if(gameInfo.variant == VariantShogi) {
5803         promotionZoneSize = BOARD_HEIGHT/3;
5804         highestPromotingPiece = (int)WhiteFerz;
5805     } else if(gameInfo.variant == VariantMakruk) {
5806         promotionZoneSize = 3;
5807     }
5808
5809     // Treat Lance as Pawn when it is not representing Amazon
5810     if(gameInfo.variant != VariantSuper) {
5811         if(piece == WhiteLance) piece = WhitePawn; else
5812         if(piece == BlackLance) piece = BlackPawn;
5813     }
5814
5815     // next weed out all moves that do not touch the promotion zone at all
5816     if((int)piece >= BlackPawn) {
5817         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5818              return FALSE;
5819         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5820     } else {
5821         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5822            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5823     }
5824
5825     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5826
5827     // weed out mandatory Shogi promotions
5828     if(gameInfo.variant == VariantShogi) {
5829         if(piece >= BlackPawn) {
5830             if(toY == 0 && piece == BlackPawn ||
5831                toY == 0 && piece == BlackQueen ||
5832                toY <= 1 && piece == BlackKnight) {
5833                 *promoChoice = '+';
5834                 return FALSE;
5835             }
5836         } else {
5837             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5838                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5839                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5840                 *promoChoice = '+';
5841                 return FALSE;
5842             }
5843         }
5844     }
5845
5846     // weed out obviously illegal Pawn moves
5847     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5848         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5849         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5850         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5851         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5852         // note we are not allowed to test for valid (non-)capture, due to premove
5853     }
5854
5855     // we either have a choice what to promote to, or (in Shogi) whether to promote
5856     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5857         *promoChoice = PieceToChar(BlackFerz);  // no choice
5858         return FALSE;
5859     }
5860     // no sense asking what we must promote to if it is going to explode...
5861     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5862         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5863         return FALSE;
5864     }
5865     // give caller the default choice even if we will not make it
5866     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5867     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5868     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5869                            && gameInfo.variant != VariantShogi
5870                            && gameInfo.variant != VariantSuper) return FALSE;
5871     if(autoQueen) return FALSE; // predetermined
5872
5873     // suppress promotion popup on illegal moves that are not premoves
5874     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5875               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5876     if(appData.testLegality && !premove) {
5877         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5878                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5879         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5880             return FALSE;
5881     }
5882
5883     return TRUE;
5884 }
5885
5886 int
5887 InPalace(row, column)
5888      int row, column;
5889 {   /* [HGM] for Xiangqi */
5890     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5891          column < (BOARD_WIDTH + 4)/2 &&
5892          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5893     return FALSE;
5894 }
5895
5896 int
5897 PieceForSquare (x, y)
5898      int x;
5899      int y;
5900 {
5901   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5902      return -1;
5903   else
5904      return boards[currentMove][y][x];
5905 }
5906
5907 int
5908 OKToStartUserMove(x, y)
5909      int x, y;
5910 {
5911     ChessSquare from_piece;
5912     int white_piece;
5913
5914     if (matchMode) return FALSE;
5915     if (gameMode == EditPosition) return TRUE;
5916
5917     if (x >= 0 && y >= 0)
5918       from_piece = boards[currentMove][y][x];
5919     else
5920       from_piece = EmptySquare;
5921
5922     if (from_piece == EmptySquare) return FALSE;
5923
5924     white_piece = (int)from_piece >= (int)WhitePawn &&
5925       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5926
5927     switch (gameMode) {
5928       case PlayFromGameFile:
5929       case AnalyzeFile:
5930       case TwoMachinesPlay:
5931       case EndOfGame:
5932         return FALSE;
5933
5934       case IcsObserving:
5935       case IcsIdle:
5936         return FALSE;
5937
5938       case MachinePlaysWhite:
5939       case IcsPlayingBlack:
5940         if (appData.zippyPlay) return FALSE;
5941         if (white_piece) {
5942             DisplayMoveError(_("You are playing Black"));
5943             return FALSE;
5944         }
5945         break;
5946
5947       case MachinePlaysBlack:
5948       case IcsPlayingWhite:
5949         if (appData.zippyPlay) return FALSE;
5950         if (!white_piece) {
5951             DisplayMoveError(_("You are playing White"));
5952             return FALSE;
5953         }
5954         break;
5955
5956       case EditGame:
5957         if (!white_piece && WhiteOnMove(currentMove)) {
5958             DisplayMoveError(_("It is White's turn"));
5959             return FALSE;
5960         }
5961         if (white_piece && !WhiteOnMove(currentMove)) {
5962             DisplayMoveError(_("It is Black's turn"));
5963             return FALSE;
5964         }
5965         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5966             /* Editing correspondence game history */
5967             /* Could disallow this or prompt for confirmation */
5968             cmailOldMove = -1;
5969         }
5970         break;
5971
5972       case BeginningOfGame:
5973         if (appData.icsActive) return FALSE;
5974         if (!appData.noChessProgram) {
5975             if (!white_piece) {
5976                 DisplayMoveError(_("You are playing White"));
5977                 return FALSE;
5978             }
5979         }
5980         break;
5981
5982       case Training:
5983         if (!white_piece && WhiteOnMove(currentMove)) {
5984             DisplayMoveError(_("It is White's turn"));
5985             return FALSE;
5986         }
5987         if (white_piece && !WhiteOnMove(currentMove)) {
5988             DisplayMoveError(_("It is Black's turn"));
5989             return FALSE;
5990         }
5991         break;
5992
5993       default:
5994       case IcsExamining:
5995         break;
5996     }
5997     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5998         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5999         && gameMode != AnalyzeFile && gameMode != Training) {
6000         DisplayMoveError(_("Displayed position is not current"));
6001         return FALSE;
6002     }
6003     return TRUE;
6004 }
6005
6006 Boolean
6007 OnlyMove(int *x, int *y, Boolean captures) {
6008     DisambiguateClosure cl;
6009     if (appData.zippyPlay) return FALSE;
6010     switch(gameMode) {
6011       case MachinePlaysBlack:
6012       case IcsPlayingWhite:
6013       case BeginningOfGame:
6014         if(!WhiteOnMove(currentMove)) return FALSE;
6015         break;
6016       case MachinePlaysWhite:
6017       case IcsPlayingBlack:
6018         if(WhiteOnMove(currentMove)) return FALSE;
6019         break;
6020       case EditGame:
6021         break;
6022       default:
6023         return FALSE;
6024     }
6025     cl.pieceIn = EmptySquare;
6026     cl.rfIn = *y;
6027     cl.ffIn = *x;
6028     cl.rtIn = -1;
6029     cl.ftIn = -1;
6030     cl.promoCharIn = NULLCHAR;
6031     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6032     if( cl.kind == NormalMove ||
6033         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6034         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6035         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6036       fromX = cl.ff;
6037       fromY = cl.rf;
6038       *x = cl.ft;
6039       *y = cl.rt;
6040       return TRUE;
6041     }
6042     if(cl.kind != ImpossibleMove) return FALSE;
6043     cl.pieceIn = EmptySquare;
6044     cl.rfIn = -1;
6045     cl.ffIn = -1;
6046     cl.rtIn = *y;
6047     cl.ftIn = *x;
6048     cl.promoCharIn = NULLCHAR;
6049     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6050     if( cl.kind == NormalMove ||
6051         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6052         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6053         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6054       fromX = cl.ff;
6055       fromY = cl.rf;
6056       *x = cl.ft;
6057       *y = cl.rt;
6058       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6059       return TRUE;
6060     }
6061     return FALSE;
6062 }
6063
6064 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6065 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6066 int lastLoadGameUseList = FALSE;
6067 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6068 ChessMove lastLoadGameStart = EndOfFile;
6069
6070 void
6071 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6072      int fromX, fromY, toX, toY;
6073      int promoChar;
6074 {
6075     ChessMove moveType;
6076     ChessSquare pdown, pup;
6077
6078     /* Check if the user is playing in turn.  This is complicated because we
6079        let the user "pick up" a piece before it is his turn.  So the piece he
6080        tried to pick up may have been captured by the time he puts it down!
6081        Therefore we use the color the user is supposed to be playing in this
6082        test, not the color of the piece that is currently on the starting
6083        square---except in EditGame mode, where the user is playing both
6084        sides; fortunately there the capture race can't happen.  (It can
6085        now happen in IcsExamining mode, but that's just too bad.  The user
6086        will get a somewhat confusing message in that case.)
6087        */
6088
6089     switch (gameMode) {
6090       case PlayFromGameFile:
6091       case AnalyzeFile:
6092       case TwoMachinesPlay:
6093       case EndOfGame:
6094       case IcsObserving:
6095       case IcsIdle:
6096         /* We switched into a game mode where moves are not accepted,
6097            perhaps while the mouse button was down. */
6098         return;
6099
6100       case MachinePlaysWhite:
6101         /* User is moving for Black */
6102         if (WhiteOnMove(currentMove)) {
6103             DisplayMoveError(_("It is White's turn"));
6104             return;
6105         }
6106         break;
6107
6108       case MachinePlaysBlack:
6109         /* User is moving for White */
6110         if (!WhiteOnMove(currentMove)) {
6111             DisplayMoveError(_("It is Black's turn"));
6112             return;
6113         }
6114         break;
6115
6116       case EditGame:
6117       case IcsExamining:
6118       case BeginningOfGame:
6119       case AnalyzeMode:
6120       case Training:
6121         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6122         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6123             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6124             /* User is moving for Black */
6125             if (WhiteOnMove(currentMove)) {
6126                 DisplayMoveError(_("It is White's turn"));
6127                 return;
6128             }
6129         } else {
6130             /* User is moving for White */
6131             if (!WhiteOnMove(currentMove)) {
6132                 DisplayMoveError(_("It is Black's turn"));
6133                 return;
6134             }
6135         }
6136         break;
6137
6138       case IcsPlayingBlack:
6139         /* User is moving for Black */
6140         if (WhiteOnMove(currentMove)) {
6141             if (!appData.premove) {
6142                 DisplayMoveError(_("It is White's turn"));
6143             } else if (toX >= 0 && toY >= 0) {
6144                 premoveToX = toX;
6145                 premoveToY = toY;
6146                 premoveFromX = fromX;
6147                 premoveFromY = fromY;
6148                 premovePromoChar = promoChar;
6149                 gotPremove = 1;
6150                 if (appData.debugMode)
6151                     fprintf(debugFP, "Got premove: fromX %d,"
6152                             "fromY %d, toX %d, toY %d\n",
6153                             fromX, fromY, toX, toY);
6154             }
6155             return;
6156         }
6157         break;
6158
6159       case IcsPlayingWhite:
6160         /* User is moving for White */
6161         if (!WhiteOnMove(currentMove)) {
6162             if (!appData.premove) {
6163                 DisplayMoveError(_("It is Black's turn"));
6164             } else if (toX >= 0 && toY >= 0) {
6165                 premoveToX = toX;
6166                 premoveToY = toY;
6167                 premoveFromX = fromX;
6168                 premoveFromY = fromY;
6169                 premovePromoChar = promoChar;
6170                 gotPremove = 1;
6171                 if (appData.debugMode)
6172                     fprintf(debugFP, "Got premove: fromX %d,"
6173                             "fromY %d, toX %d, toY %d\n",
6174                             fromX, fromY, toX, toY);
6175             }
6176             return;
6177         }
6178         break;
6179
6180       default:
6181         break;
6182
6183       case EditPosition:
6184         /* EditPosition, empty square, or different color piece;
6185            click-click move is possible */
6186         if (toX == -2 || toY == -2) {
6187             boards[0][fromY][fromX] = EmptySquare;
6188             DrawPosition(FALSE, boards[currentMove]);
6189             return;
6190         } else if (toX >= 0 && toY >= 0) {
6191             boards[0][toY][toX] = boards[0][fromY][fromX];
6192             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6193                 if(boards[0][fromY][0] != EmptySquare) {
6194                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6195                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6196                 }
6197             } else
6198             if(fromX == BOARD_RGHT+1) {
6199                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6200                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6201                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6202                 }
6203             } else
6204             boards[0][fromY][fromX] = EmptySquare;
6205             DrawPosition(FALSE, boards[currentMove]);
6206             return;
6207         }
6208         return;
6209     }
6210
6211     if(toX < 0 || toY < 0) return;
6212     pdown = boards[currentMove][fromY][fromX];
6213     pup = boards[currentMove][toY][toX];
6214
6215     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6216     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6217          if( pup != EmptySquare ) return;
6218          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6219            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6220                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6221            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6222            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6223            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6224            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6225          fromY = DROP_RANK;
6226     }
6227
6228     /* [HGM] always test for legality, to get promotion info */
6229     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6230                                          fromY, fromX, toY, toX, promoChar);
6231     /* [HGM] but possibly ignore an IllegalMove result */
6232     if (appData.testLegality) {
6233         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6234             DisplayMoveError(_("Illegal move"));
6235             return;
6236         }
6237     }
6238
6239     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6240 }
6241
6242 /* Common tail of UserMoveEvent and DropMenuEvent */
6243 int
6244 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6245      ChessMove moveType;
6246      int fromX, fromY, toX, toY;
6247      /*char*/int promoChar;
6248 {
6249     char *bookHit = 0;
6250
6251     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6252         // [HGM] superchess: suppress promotions to non-available piece
6253         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6254         if(WhiteOnMove(currentMove)) {
6255             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6256         } else {
6257             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6258         }
6259     }
6260
6261     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6262        move type in caller when we know the move is a legal promotion */
6263     if(moveType == NormalMove && promoChar)
6264         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6265
6266     /* [HGM] <popupFix> The following if has been moved here from
6267        UserMoveEvent(). Because it seemed to belong here (why not allow
6268        piece drops in training games?), and because it can only be
6269        performed after it is known to what we promote. */
6270     if (gameMode == Training) {
6271       /* compare the move played on the board to the next move in the
6272        * game. If they match, display the move and the opponent's response.
6273        * If they don't match, display an error message.
6274        */
6275       int saveAnimate;
6276       Board testBoard;
6277       CopyBoard(testBoard, boards[currentMove]);
6278       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6279
6280       if (CompareBoards(testBoard, boards[currentMove+1])) {
6281         ForwardInner(currentMove+1);
6282
6283         /* Autoplay the opponent's response.
6284          * if appData.animate was TRUE when Training mode was entered,
6285          * the response will be animated.
6286          */
6287         saveAnimate = appData.animate;
6288         appData.animate = animateTraining;
6289         ForwardInner(currentMove+1);
6290         appData.animate = saveAnimate;
6291
6292         /* check for the end of the game */
6293         if (currentMove >= forwardMostMove) {
6294           gameMode = PlayFromGameFile;
6295           ModeHighlight();
6296           SetTrainingModeOff();
6297           DisplayInformation(_("End of game"));
6298         }
6299       } else {
6300         DisplayError(_("Incorrect move"), 0);
6301       }
6302       return 1;
6303     }
6304
6305   /* Ok, now we know that the move is good, so we can kill
6306      the previous line in Analysis Mode */
6307   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6308                                 && currentMove < forwardMostMove) {
6309     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6310     else forwardMostMove = currentMove;
6311   }
6312
6313   /* If we need the chess program but it's dead, restart it */
6314   ResurrectChessProgram();
6315
6316   /* A user move restarts a paused game*/
6317   if (pausing)
6318     PauseEvent();
6319
6320   thinkOutput[0] = NULLCHAR;
6321
6322   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6323
6324   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6325     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6326     return 1;
6327   }
6328
6329   if (gameMode == BeginningOfGame) {
6330     if (appData.noChessProgram) {
6331       gameMode = EditGame;
6332       SetGameInfo();
6333     } else {
6334       char buf[MSG_SIZ];
6335       gameMode = MachinePlaysBlack;
6336       StartClocks();
6337       SetGameInfo();
6338       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6339       DisplayTitle(buf);
6340       if (first.sendName) {
6341         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6342         SendToProgram(buf, &first);
6343       }
6344       StartClocks();
6345     }
6346     ModeHighlight();
6347   }
6348
6349   /* Relay move to ICS or chess engine */
6350   if (appData.icsActive) {
6351     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6352         gameMode == IcsExamining) {
6353       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6354         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6355         SendToICS("draw ");
6356         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6357       }
6358       // also send plain move, in case ICS does not understand atomic claims
6359       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6360       ics_user_moved = 1;
6361     }
6362   } else {
6363     if (first.sendTime && (gameMode == BeginningOfGame ||
6364                            gameMode == MachinePlaysWhite ||
6365                            gameMode == MachinePlaysBlack)) {
6366       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6367     }
6368     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6369          // [HGM] book: if program might be playing, let it use book
6370         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6371         first.maybeThinking = TRUE;
6372     } else SendMoveToProgram(forwardMostMove-1, &first);
6373     if (currentMove == cmailOldMove + 1) {
6374       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6375     }
6376   }
6377
6378   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379
6380   switch (gameMode) {
6381   case EditGame:
6382     if(appData.testLegality)
6383     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6384     case MT_NONE:
6385     case MT_CHECK:
6386       break;
6387     case MT_CHECKMATE:
6388     case MT_STAINMATE:
6389       if (WhiteOnMove(currentMove)) {
6390         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6391       } else {
6392         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6393       }
6394       break;
6395     case MT_STALEMATE:
6396       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6397       break;
6398     }
6399     break;
6400
6401   case MachinePlaysBlack:
6402   case MachinePlaysWhite:
6403     /* disable certain menu options while machine is thinking */
6404     SetMachineThinkingEnables();
6405     break;
6406
6407   default:
6408     break;
6409   }
6410
6411   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6412   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6413
6414   if(bookHit) { // [HGM] book: simulate book reply
6415         static char bookMove[MSG_SIZ]; // a bit generous?
6416
6417         programStats.nodes = programStats.depth = programStats.time =
6418         programStats.score = programStats.got_only_move = 0;
6419         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6420
6421         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6422         strcat(bookMove, bookHit);
6423         HandleMachineMove(bookMove, &first);
6424   }
6425   return 1;
6426 }
6427
6428 void
6429 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6430      Board board;
6431      int flags;
6432      ChessMove kind;
6433      int rf, ff, rt, ft;
6434      VOIDSTAR closure;
6435 {
6436     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6437     Markers *m = (Markers *) closure;
6438     if(rf == fromY && ff == fromX)
6439         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6440                          || kind == WhiteCapturesEnPassant
6441                          || kind == BlackCapturesEnPassant);
6442     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6443 }
6444
6445 void
6446 MarkTargetSquares(int clear)
6447 {
6448   int x, y;
6449   if(!appData.markers || !appData.highlightDragging ||
6450      !appData.testLegality || gameMode == EditPosition) return;
6451   if(clear) {
6452     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6453   } else {
6454     int capt = 0;
6455     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6456     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6457       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6458       if(capt)
6459       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6460     }
6461   }
6462   DrawPosition(TRUE, NULL);
6463 }
6464
6465 int
6466 Explode(Board board, int fromX, int fromY, int toX, int toY)
6467 {
6468     if(gameInfo.variant == VariantAtomic &&
6469        (board[toY][toX] != EmptySquare ||                     // capture?
6470         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6471                          board[fromY][fromX] == BlackPawn   )
6472       )) {
6473         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6474         return TRUE;
6475     }
6476     return FALSE;
6477 }
6478
6479 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6480
6481 int CanPromote(ChessSquare piece, int y)
6482 {
6483         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6484         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6485         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6486            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6487            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6488                                                   gameInfo.variant == VariantMakruk) return FALSE;
6489         return (piece == BlackPawn && y == 1 ||
6490                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6491                 piece == BlackLance && y == 1 ||
6492                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6493 }
6494
6495 void LeftClick(ClickType clickType, int xPix, int yPix)
6496 {
6497     int x, y;
6498     Boolean saveAnimate;
6499     static int second = 0, promotionChoice = 0, clearFlag = 0;
6500     char promoChoice = NULLCHAR;
6501     ChessSquare piece;
6502
6503     if(appData.seekGraph && appData.icsActive && loggedOn &&
6504         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6505         SeekGraphClick(clickType, xPix, yPix, 0);
6506         return;
6507     }
6508
6509     if (clickType == Press) ErrorPopDown();
6510     MarkTargetSquares(1);
6511
6512     x = EventToSquare(xPix, BOARD_WIDTH);
6513     y = EventToSquare(yPix, BOARD_HEIGHT);
6514     if (!flipView && y >= 0) {
6515         y = BOARD_HEIGHT - 1 - y;
6516     }
6517     if (flipView && x >= 0) {
6518         x = BOARD_WIDTH - 1 - x;
6519     }
6520
6521     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6522         defaultPromoChoice = promoSweep;
6523         promoSweep = EmptySquare;   // terminate sweep
6524         promoDefaultAltered = TRUE;
6525         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6526     }
6527
6528     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6529         if(clickType == Release) return; // ignore upclick of click-click destination
6530         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6531         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6532         if(gameInfo.holdingsWidth &&
6533                 (WhiteOnMove(currentMove)
6534                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6535                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6536             // click in right holdings, for determining promotion piece
6537             ChessSquare p = boards[currentMove][y][x];
6538             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6539             if(p != EmptySquare) {
6540                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6541                 fromX = fromY = -1;
6542                 return;
6543             }
6544         }
6545         DrawPosition(FALSE, boards[currentMove]);
6546         return;
6547     }
6548
6549     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6550     if(clickType == Press
6551             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6552               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6553               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6554         return;
6555
6556     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6557         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6558
6559     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6560         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6561                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6562         defaultPromoChoice = DefaultPromoChoice(side);
6563     }
6564
6565     autoQueen = appData.alwaysPromoteToQueen;
6566
6567     if (fromX == -1) {
6568       int originalY = y;
6569       gatingPiece = EmptySquare;
6570       if (clickType != Press) {
6571         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6572             DragPieceEnd(xPix, yPix); dragging = 0;
6573             DrawPosition(FALSE, NULL);
6574         }
6575         return;
6576       }
6577       fromX = x; fromY = y;
6578       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6579          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6580          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6581             /* First square */
6582             if (OKToStartUserMove(fromX, fromY)) {
6583                 second = 0;
6584                 MarkTargetSquares(0);
6585                 DragPieceBegin(xPix, yPix); dragging = 1;
6586                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6587                     promoSweep = defaultPromoChoice;
6588                     selectFlag = 0; lastX = xPix; lastY = yPix;
6589                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6590                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6591                 }
6592                 if (appData.highlightDragging) {
6593                     SetHighlights(fromX, fromY, -1, -1);
6594                 }
6595             } else fromX = fromY = -1;
6596             return;
6597         }
6598     }
6599
6600     /* fromX != -1 */
6601     if (clickType == Press && gameMode != EditPosition) {
6602         ChessSquare fromP;
6603         ChessSquare toP;
6604         int frc;
6605
6606         // ignore off-board to clicks
6607         if(y < 0 || x < 0) return;
6608
6609         /* Check if clicking again on the same color piece */
6610         fromP = boards[currentMove][fromY][fromX];
6611         toP = boards[currentMove][y][x];
6612         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6613         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6614              WhitePawn <= toP && toP <= WhiteKing &&
6615              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6616              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6617             (BlackPawn <= fromP && fromP <= BlackKing &&
6618              BlackPawn <= toP && toP <= BlackKing &&
6619              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6620              !(fromP == BlackKing && toP == BlackRook && frc))) {
6621             /* Clicked again on same color piece -- changed his mind */
6622             second = (x == fromX && y == fromY);
6623             promoDefaultAltered = FALSE;
6624            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6625             if (appData.highlightDragging) {
6626                 SetHighlights(x, y, -1, -1);
6627             } else {
6628                 ClearHighlights();
6629             }
6630             if (OKToStartUserMove(x, y)) {
6631                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6632                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6633                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6634                  gatingPiece = boards[currentMove][fromY][fromX];
6635                 else gatingPiece = EmptySquare;
6636                 fromX = x;
6637                 fromY = y; dragging = 1;
6638                 MarkTargetSquares(0);
6639                 DragPieceBegin(xPix, yPix);
6640                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6641                     promoSweep = defaultPromoChoice;
6642                     selectFlag = 0; lastX = xPix; lastY = yPix;
6643                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6644                 }
6645             }
6646            }
6647            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6648            second = FALSE; 
6649         }
6650         // ignore clicks on holdings
6651         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6652     }
6653
6654     if (clickType == Release && x == fromX && y == fromY) {
6655         DragPieceEnd(xPix, yPix); dragging = 0;
6656         if(clearFlag) {
6657             // a deferred attempt to click-click move an empty square on top of a piece
6658             boards[currentMove][y][x] = EmptySquare;
6659             ClearHighlights();
6660             DrawPosition(FALSE, boards[currentMove]);
6661             fromX = fromY = -1; clearFlag = 0;
6662             return;
6663         }
6664         if (appData.animateDragging) {
6665             /* Undo animation damage if any */
6666             DrawPosition(FALSE, NULL);
6667         }
6668         if (second) {
6669             /* Second up/down in same square; just abort move */
6670             second = 0;
6671             fromX = fromY = -1;
6672             gatingPiece = EmptySquare;
6673             ClearHighlights();
6674             gotPremove = 0;
6675             ClearPremoveHighlights();
6676         } else {
6677             /* First upclick in same square; start click-click mode */
6678             SetHighlights(x, y, -1, -1);
6679         }
6680         return;
6681     }
6682
6683     clearFlag = 0;
6684
6685     /* we now have a different from- and (possibly off-board) to-square */
6686     /* Completed move */
6687     toX = x;
6688     toY = y;
6689     saveAnimate = appData.animate;
6690     if (clickType == Press) {
6691         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6692             // must be Edit Position mode with empty-square selected
6693             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6694             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6695             return;
6696         }
6697         /* Finish clickclick move */
6698         if (appData.animate || appData.highlightLastMove) {
6699             SetHighlights(fromX, fromY, toX, toY);
6700         } else {
6701             ClearHighlights();
6702         }
6703     } else {
6704         /* Finish drag move */
6705         if (appData.highlightLastMove) {
6706             SetHighlights(fromX, fromY, toX, toY);
6707         } else {
6708             ClearHighlights();
6709         }
6710         DragPieceEnd(xPix, yPix); dragging = 0;
6711         /* Don't animate move and drag both */
6712         appData.animate = FALSE;
6713     }
6714
6715     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6716     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6717         ChessSquare piece = boards[currentMove][fromY][fromX];
6718         if(gameMode == EditPosition && piece != EmptySquare &&
6719            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6720             int n;
6721
6722             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6723                 n = PieceToNumber(piece - (int)BlackPawn);
6724                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6725                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6726                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6727             } else
6728             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6729                 n = PieceToNumber(piece);
6730                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6731                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6732                 boards[currentMove][n][BOARD_WIDTH-2]++;
6733             }
6734             boards[currentMove][fromY][fromX] = EmptySquare;
6735         }
6736         ClearHighlights();
6737         fromX = fromY = -1;
6738         DrawPosition(TRUE, boards[currentMove]);
6739         return;
6740     }
6741
6742     // off-board moves should not be highlighted
6743     if(x < 0 || y < 0) ClearHighlights();
6744
6745     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6746
6747     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6748         SetHighlights(fromX, fromY, toX, toY);
6749         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6750             // [HGM] super: promotion to captured piece selected from holdings
6751             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6752             promotionChoice = TRUE;
6753             // kludge follows to temporarily execute move on display, without promoting yet
6754             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6755             boards[currentMove][toY][toX] = p;
6756             DrawPosition(FALSE, boards[currentMove]);
6757             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6758             boards[currentMove][toY][toX] = q;
6759             DisplayMessage("Click in holdings to choose piece", "");
6760             return;
6761         }
6762         PromotionPopUp();
6763     } else {
6764         int oldMove = currentMove;
6765         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6766         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6767         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6768         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6769            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6770             DrawPosition(TRUE, boards[currentMove]);
6771         fromX = fromY = -1;
6772     }
6773     appData.animate = saveAnimate;
6774     if (appData.animate || appData.animateDragging) {
6775         /* Undo animation damage if needed */
6776         DrawPosition(FALSE, NULL);
6777     }
6778 }
6779
6780 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6781 {   // front-end-free part taken out of PieceMenuPopup
6782     int whichMenu; int xSqr, ySqr;
6783
6784     if(seekGraphUp) { // [HGM] seekgraph
6785         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6786         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6787         return -2;
6788     }
6789
6790     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6791          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6792         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6793         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6794         if(action == Press)   {
6795             originalFlip = flipView;
6796             flipView = !flipView; // temporarily flip board to see game from partners perspective
6797             DrawPosition(TRUE, partnerBoard);
6798             DisplayMessage(partnerStatus, "");
6799             partnerUp = TRUE;
6800         } else if(action == Release) {
6801             flipView = originalFlip;
6802             DrawPosition(TRUE, boards[currentMove]);
6803             partnerUp = FALSE;
6804         }
6805         return -2;
6806     }
6807
6808     xSqr = EventToSquare(x, BOARD_WIDTH);
6809     ySqr = EventToSquare(y, BOARD_HEIGHT);
6810     if (action == Release) {
6811         if(pieceSweep != EmptySquare) {
6812             EditPositionMenuEvent(pieceSweep, toX, toY);
6813             pieceSweep = EmptySquare;
6814         } else UnLoadPV(); // [HGM] pv
6815     }
6816     if (action != Press) return -2; // return code to be ignored
6817     switch (gameMode) {
6818       case IcsExamining:
6819         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6820       case EditPosition:
6821         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6822         if (xSqr < 0 || ySqr < 0) return -1;
6823         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6824         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6825         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6826         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6827         NextPiece(0);
6828         return -2;\r
6829       case IcsObserving:
6830         if(!appData.icsEngineAnalyze) return -1;
6831       case IcsPlayingWhite:
6832       case IcsPlayingBlack:
6833         if(!appData.zippyPlay) goto noZip;
6834       case AnalyzeMode:
6835       case AnalyzeFile:
6836       case MachinePlaysWhite:
6837       case MachinePlaysBlack:
6838       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6839         if (!appData.dropMenu) {
6840           LoadPV(x, y);
6841           return 2; // flag front-end to grab mouse events
6842         }
6843         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6844            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6845       case EditGame:
6846       noZip:
6847         if (xSqr < 0 || ySqr < 0) return -1;
6848         if (!appData.dropMenu || appData.testLegality &&
6849             gameInfo.variant != VariantBughouse &&
6850             gameInfo.variant != VariantCrazyhouse) return -1;
6851         whichMenu = 1; // drop menu
6852         break;
6853       default:
6854         return -1;
6855     }
6856
6857     if (((*fromX = xSqr) < 0) ||
6858         ((*fromY = ySqr) < 0)) {
6859         *fromX = *fromY = -1;
6860         return -1;
6861     }
6862     if (flipView)
6863       *fromX = BOARD_WIDTH - 1 - *fromX;
6864     else
6865       *fromY = BOARD_HEIGHT - 1 - *fromY;
6866
6867     return whichMenu;
6868 }
6869
6870 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6871 {
6872 //    char * hint = lastHint;
6873     FrontEndProgramStats stats;
6874
6875     stats.which = cps == &first ? 0 : 1;
6876     stats.depth = cpstats->depth;
6877     stats.nodes = cpstats->nodes;
6878     stats.score = cpstats->score;
6879     stats.time = cpstats->time;
6880     stats.pv = cpstats->movelist;
6881     stats.hint = lastHint;
6882     stats.an_move_index = 0;
6883     stats.an_move_count = 0;
6884
6885     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6886         stats.hint = cpstats->move_name;
6887         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6888         stats.an_move_count = cpstats->nr_moves;
6889     }
6890
6891     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
6892
6893     SetProgramStats( &stats );
6894 }
6895
6896 void
6897 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6898 {       // count all piece types
6899         int p, f, r;
6900         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6901         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6902         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6903                 p = board[r][f];
6904                 pCnt[p]++;
6905                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6906                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6907                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6908                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6909                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6910                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6911         }
6912 }
6913
6914 int
6915 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6916 {
6917         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6918         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6919
6920         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6921         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6922         if(myPawns == 2 && nMine == 3) // KPP
6923             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6924         if(myPawns == 1 && nMine == 2) // KP
6925             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6926         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6927             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6928         if(myPawns) return FALSE;
6929         if(pCnt[WhiteRook+side])
6930             return pCnt[BlackRook-side] ||
6931                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6932                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6933                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6934         if(pCnt[WhiteCannon+side]) {
6935             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6936             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6937         }
6938         if(pCnt[WhiteKnight+side])
6939             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6940         return FALSE;
6941 }
6942
6943 int
6944 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6945 {
6946         VariantClass v = gameInfo.variant;
6947
6948         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6949         if(v == VariantShatranj) return TRUE; // always winnable through baring
6950         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6951         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6952
6953         if(v == VariantXiangqi) {
6954                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6955
6956                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6957                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6958                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6959                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6960                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6961                 if(stale) // we have at least one last-rank P plus perhaps C
6962                     return majors // KPKX
6963                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6964                 else // KCA*E*
6965                     return pCnt[WhiteFerz+side] // KCAK
6966                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6967                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6968                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6969
6970         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6971                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6972
6973                 if(nMine == 1) return FALSE; // bare King
6974                 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
6975                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6976                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6977                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6978                 if(pCnt[WhiteKnight+side])
6979                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6980                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6981                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6982                 if(nBishops)
6983                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6984                 if(pCnt[WhiteAlfil+side])
6985                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6986                 if(pCnt[WhiteWazir+side])
6987                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6988         }
6989
6990         return TRUE;
6991 }
6992
6993 int
6994 Adjudicate(ChessProgramState *cps)
6995 {       // [HGM] some adjudications useful with buggy engines
6996         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6997         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6998         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6999         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7000         int k, count = 0; static int bare = 1;
7001         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7002         Boolean canAdjudicate = !appData.icsActive;
7003
7004         // most tests only when we understand the game, i.e. legality-checking on
7005             if( appData.testLegality )
7006             {   /* [HGM] Some more adjudications for obstinate engines */
7007                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7008                 static int moveCount = 6;
7009                 ChessMove result;
7010                 char *reason = NULL;
7011
7012                 /* Count what is on board. */
7013                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7014
7015                 /* Some material-based adjudications that have to be made before stalemate test */
7016                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7017                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7018                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7019                      if(canAdjudicate && appData.checkMates) {
7020                          if(engineOpponent)
7021                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7022                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7023                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7024                          return 1;
7025                      }
7026                 }
7027
7028                 /* Bare King in Shatranj (loses) or Losers (wins) */
7029                 if( nrW == 1 || nrB == 1) {
7030                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7031                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7032                      if(canAdjudicate && appData.checkMates) {
7033                          if(engineOpponent)
7034                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7035                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7036                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7037                          return 1;
7038                      }
7039                   } else
7040                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7041                   {    /* bare King */
7042                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7043                         if(canAdjudicate && appData.checkMates) {
7044                             /* but only adjudicate if adjudication enabled */
7045                             if(engineOpponent)
7046                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7047                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7048                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7049                             return 1;
7050                         }
7051                   }
7052                 } else bare = 1;
7053
7054
7055             // don't wait for engine to announce game end if we can judge ourselves
7056             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7057               case MT_CHECK:
7058                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7059                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7060                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7061                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7062                             checkCnt++;
7063                         if(checkCnt >= 2) {
7064                             reason = "Xboard adjudication: 3rd check";
7065                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7066                             break;
7067                         }
7068                     }
7069                 }
7070               case MT_NONE:
7071               default:
7072                 break;
7073               case MT_STALEMATE:
7074               case MT_STAINMATE:
7075                 reason = "Xboard adjudication: Stalemate";
7076                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7077                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7078                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7079                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7080                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7081                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7082                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7083                                                                         EP_CHECKMATE : EP_WINS);
7084                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7085                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7086                 }
7087                 break;
7088               case MT_CHECKMATE:
7089                 reason = "Xboard adjudication: Checkmate";
7090                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7091                 break;
7092             }
7093
7094                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7095                     case EP_STALEMATE:
7096                         result = GameIsDrawn; break;
7097                     case EP_CHECKMATE:
7098                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7099                     case EP_WINS:
7100                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7101                     default:
7102                         result = EndOfFile;
7103                 }
7104                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7105                     if(engineOpponent)
7106                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7107                     GameEnds( result, reason, GE_XBOARD );
7108                     return 1;
7109                 }
7110
7111                 /* Next absolutely insufficient mating material. */
7112                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7113                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7114                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7115
7116                      /* always flag draws, for judging claims */
7117                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7118
7119                      if(canAdjudicate && appData.materialDraws) {
7120                          /* but only adjudicate them if adjudication enabled */
7121                          if(engineOpponent) {
7122                            SendToProgram("force\n", engineOpponent); // suppress reply
7123                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7124                          }
7125                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7126                          return 1;
7127                      }
7128                 }
7129
7130                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7131                 if(gameInfo.variant == VariantXiangqi ?
7132                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7133                  : nrW + nrB == 4 &&
7134                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7135                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7136                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7137                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7138                    ) ) {
7139                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7140                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7141                           if(engineOpponent) {
7142                             SendToProgram("force\n", engineOpponent); // suppress reply
7143                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7144                           }
7145                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7146                           return 1;
7147                      }
7148                 } else moveCount = 6;
7149             }
7150         if (appData.debugMode) { int i;
7151             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7152                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7153                     appData.drawRepeats);
7154             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7155               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7156
7157         }
7158
7159         // Repetition draws and 50-move rule can be applied independently of legality testing
7160
7161                 /* Check for rep-draws */
7162                 count = 0;
7163                 for(k = forwardMostMove-2;
7164                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7165                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7166                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7167                     k-=2)
7168                 {   int rights=0;
7169                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7170                         /* compare castling rights */
7171                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7172                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7173                                 rights++; /* King lost rights, while rook still had them */
7174                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7175                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7176                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7177                                    rights++; /* but at least one rook lost them */
7178                         }
7179                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7180                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7181                                 rights++;
7182                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7183                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7184                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7185                                    rights++;
7186                         }
7187                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7188                             && appData.drawRepeats > 1) {
7189                              /* adjudicate after user-specified nr of repeats */
7190                              int result = GameIsDrawn;
7191                              char *details = "XBoard adjudication: repetition draw";
7192                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7193                                 // [HGM] xiangqi: check for forbidden perpetuals
7194                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7195                                 for(m=forwardMostMove; m>k; m-=2) {
7196                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7197                                         ourPerpetual = 0; // the current mover did not always check
7198                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7199                                         hisPerpetual = 0; // the opponent did not always check
7200                                 }
7201                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7202                                                                         ourPerpetual, hisPerpetual);
7203                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7204                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7205                                     details = "Xboard adjudication: perpetual checking";
7206                                 } else
7207                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7208                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7209                                 } else
7210                                 // Now check for perpetual chases
7211                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7212                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7213                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7214                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7215                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7216                                         details = "Xboard adjudication: perpetual chasing";
7217                                     } else
7218                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7219                                         break; // Abort repetition-checking loop.
7220                                 }
7221                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7222                              }
7223                              if(engineOpponent) {
7224                                SendToProgram("force\n", engineOpponent); // suppress reply
7225                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7226                              }
7227                              GameEnds( result, details, GE_XBOARD );
7228                              return 1;
7229                         }
7230                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7231                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7232                     }
7233                 }
7234
7235                 /* Now we test for 50-move draws. Determine ply count */
7236                 count = forwardMostMove;
7237                 /* look for last irreversble move */
7238                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7239                     count--;
7240                 /* if we hit starting position, add initial plies */
7241                 if( count == backwardMostMove )
7242                     count -= initialRulePlies;
7243                 count = forwardMostMove - count;
7244                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7245                         // adjust reversible move counter for checks in Xiangqi
7246                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7247                         if(i < backwardMostMove) i = backwardMostMove;
7248                         while(i <= forwardMostMove) {
7249                                 lastCheck = inCheck; // check evasion does not count
7250                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7251                                 if(inCheck || lastCheck) count--; // check does not count
7252                                 i++;
7253                         }
7254                 }
7255                 if( count >= 100)
7256                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7257                          /* this is used to judge if draw claims are legal */
7258                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7259                          if(engineOpponent) {
7260                            SendToProgram("force\n", engineOpponent); // suppress reply
7261                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7262                          }
7263                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7264                          return 1;
7265                 }
7266
7267                 /* if draw offer is pending, treat it as a draw claim
7268                  * when draw condition present, to allow engines a way to
7269                  * claim draws before making their move to avoid a race
7270                  * condition occurring after their move
7271                  */
7272                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7273                          char *p = NULL;
7274                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7275                              p = "Draw claim: 50-move rule";
7276                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7277                              p = "Draw claim: 3-fold repetition";
7278                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7279                              p = "Draw claim: insufficient mating material";
7280                          if( p != NULL && canAdjudicate) {
7281                              if(engineOpponent) {
7282                                SendToProgram("force\n", engineOpponent); // suppress reply
7283                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7284                              }
7285                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7286                              return 1;
7287                          }
7288                 }
7289
7290                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7291                     if(engineOpponent) {
7292                       SendToProgram("force\n", engineOpponent); // suppress reply
7293                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7294                     }
7295                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7296                     return 1;
7297                 }
7298         return 0;
7299 }
7300
7301 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7302 {   // [HGM] book: this routine intercepts moves to simulate book replies
7303     char *bookHit = NULL;
7304
7305     //first determine if the incoming move brings opponent into his book
7306     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7307         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7308     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7309     if(bookHit != NULL && !cps->bookSuspend) {
7310         // make sure opponent is not going to reply after receiving move to book position
7311         SendToProgram("force\n", cps);
7312         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7313     }
7314     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7315     // now arrange restart after book miss
7316     if(bookHit) {
7317         // after a book hit we never send 'go', and the code after the call to this routine
7318         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7319         char buf[MSG_SIZ];
7320         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7321         SendToProgram(buf, cps);
7322         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7323     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7324         SendToProgram("go\n", cps);
7325         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7326     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7327         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7328             SendToProgram("go\n", cps);
7329         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7330     }
7331     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7332 }
7333
7334 char *savedMessage;
7335 ChessProgramState *savedState;
7336 void DeferredBookMove(void)
7337 {
7338         if(savedState->lastPing != savedState->lastPong)
7339                     ScheduleDelayedEvent(DeferredBookMove, 10);
7340         else
7341         HandleMachineMove(savedMessage, savedState);
7342 }
7343
7344 void
7345 HandleMachineMove(message, cps)
7346      char *message;
7347      ChessProgramState *cps;
7348 {
7349     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7350     char realname[MSG_SIZ];
7351     int fromX, fromY, toX, toY;
7352     ChessMove moveType;
7353     char promoChar;
7354     char *p;
7355     int machineWhite;
7356     char *bookHit;
7357
7358     cps->userError = 0;
7359
7360 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7361     /*
7362      * Kludge to ignore BEL characters
7363      */
7364     while (*message == '\007') message++;
7365
7366     /*
7367      * [HGM] engine debug message: ignore lines starting with '#' character
7368      */
7369     if(cps->debug && *message == '#') return;
7370
7371     /*
7372      * Look for book output
7373      */
7374     if (cps == &first && bookRequested) {
7375         if (message[0] == '\t' || message[0] == ' ') {
7376             /* Part of the book output is here; append it */
7377             strcat(bookOutput, message);
7378             strcat(bookOutput, "  \n");
7379             return;
7380         } else if (bookOutput[0] != NULLCHAR) {
7381             /* All of book output has arrived; display it */
7382             char *p = bookOutput;
7383             while (*p != NULLCHAR) {
7384                 if (*p == '\t') *p = ' ';
7385                 p++;
7386             }
7387             DisplayInformation(bookOutput);
7388             bookRequested = FALSE;
7389             /* Fall through to parse the current output */
7390         }
7391     }
7392
7393     /*
7394      * Look for machine move.
7395      */
7396     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7397         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7398     {
7399         /* This method is only useful on engines that support ping */
7400         if (cps->lastPing != cps->lastPong) {
7401           if (gameMode == BeginningOfGame) {
7402             /* Extra move from before last new; ignore */
7403             if (appData.debugMode) {
7404                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7405             }
7406           } else {
7407             if (appData.debugMode) {
7408                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7409                         cps->which, gameMode);
7410             }
7411
7412             SendToProgram("undo\n", cps);
7413           }
7414           return;
7415         }
7416
7417         switch (gameMode) {
7418           case BeginningOfGame:
7419             /* Extra move from before last reset; ignore */
7420             if (appData.debugMode) {
7421                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7422             }
7423             return;
7424
7425           case EndOfGame:
7426           case IcsIdle:
7427           default:
7428             /* Extra move after we tried to stop.  The mode test is
7429                not a reliable way of detecting this problem, but it's
7430                the best we can do on engines that don't support ping.
7431             */
7432             if (appData.debugMode) {
7433                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7434                         cps->which, gameMode);
7435             }
7436             SendToProgram("undo\n", cps);
7437             return;
7438
7439           case MachinePlaysWhite:
7440           case IcsPlayingWhite:
7441             machineWhite = TRUE;
7442             break;
7443
7444           case MachinePlaysBlack:
7445           case IcsPlayingBlack:
7446             machineWhite = FALSE;
7447             break;
7448
7449           case TwoMachinesPlay:
7450             machineWhite = (cps->twoMachinesColor[0] == 'w');
7451             break;
7452         }
7453         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7454             if (appData.debugMode) {
7455                 fprintf(debugFP,
7456                         "Ignoring move out of turn by %s, gameMode %d"
7457                         ", forwardMost %d\n",
7458                         cps->which, gameMode, forwardMostMove);
7459             }
7460             return;
7461         }
7462
7463     if (appData.debugMode) { int f = forwardMostMove;
7464         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7465                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7466                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7467     }
7468         if(cps->alphaRank) AlphaRank(machineMove, 4);
7469         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7470                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7471             /* Machine move could not be parsed; ignore it. */
7472           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7473                     machineMove, _(cps->which));
7474             DisplayError(buf1, 0);
7475             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7476                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7477             if (gameMode == TwoMachinesPlay) {
7478               GameEnds(machineWhite ? BlackWins : WhiteWins,
7479                        buf1, GE_XBOARD);
7480             }
7481             return;
7482         }
7483
7484         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7485         /* So we have to redo legality test with true e.p. status here,  */
7486         /* to make sure an illegal e.p. capture does not slip through,   */
7487         /* to cause a forfeit on a justified illegal-move complaint      */
7488         /* of the opponent.                                              */
7489         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7490            ChessMove moveType;
7491            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7492                              fromY, fromX, toY, toX, promoChar);
7493             if (appData.debugMode) {
7494                 int i;
7495                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7496                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7497                 fprintf(debugFP, "castling rights\n");
7498             }
7499             if(moveType == IllegalMove) {
7500               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7501                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7502                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7503                            buf1, GE_XBOARD);
7504                 return;
7505            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7506            /* [HGM] Kludge to handle engines that send FRC-style castling
7507               when they shouldn't (like TSCP-Gothic) */
7508            switch(moveType) {
7509              case WhiteASideCastleFR:
7510              case BlackASideCastleFR:
7511                toX+=2;
7512                currentMoveString[2]++;
7513                break;
7514              case WhiteHSideCastleFR:
7515              case BlackHSideCastleFR:
7516                toX--;
7517                currentMoveString[2]--;
7518                break;
7519              default: ; // nothing to do, but suppresses warning of pedantic compilers
7520            }
7521         }
7522         hintRequested = FALSE;
7523         lastHint[0] = NULLCHAR;
7524         bookRequested = FALSE;
7525         /* Program may be pondering now */
7526         cps->maybeThinking = TRUE;
7527         if (cps->sendTime == 2) cps->sendTime = 1;
7528         if (cps->offeredDraw) cps->offeredDraw--;
7529
7530         /* [AS] Save move info*/
7531         pvInfoList[ forwardMostMove ].score = programStats.score;
7532         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7533         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7534
7535         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7536
7537         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7538         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7539             int count = 0;
7540
7541             while( count < adjudicateLossPlies ) {
7542                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7543
7544                 if( count & 1 ) {
7545                     score = -score; /* Flip score for winning side */
7546                 }
7547
7548                 if( score > adjudicateLossThreshold ) {
7549                     break;
7550                 }
7551
7552                 count++;
7553             }
7554
7555             if( count >= adjudicateLossPlies ) {
7556                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7557
7558                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7559                     "Xboard adjudication",
7560                     GE_XBOARD );
7561
7562                 return;
7563             }
7564         }
7565
7566         if(Adjudicate(cps)) {
7567             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7568             return; // [HGM] adjudicate: for all automatic game ends
7569         }
7570
7571 #if ZIPPY
7572         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7573             first.initDone) {
7574           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7575                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7576                 SendToICS("draw ");
7577                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7578           }
7579           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7580           ics_user_moved = 1;
7581           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7582                 char buf[3*MSG_SIZ];
7583
7584                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7585                         programStats.score / 100.,
7586                         programStats.depth,
7587                         programStats.time / 100.,
7588                         (unsigned int)programStats.nodes,
7589                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7590                         programStats.movelist);
7591                 SendToICS(buf);
7592 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7593           }
7594         }
7595 #endif
7596
7597         /* [AS] Clear stats for next move */
7598         ClearProgramStats();
7599         thinkOutput[0] = NULLCHAR;
7600         hiddenThinkOutputState = 0;
7601
7602         bookHit = NULL;
7603         if (gameMode == TwoMachinesPlay) {
7604             /* [HGM] relaying draw offers moved to after reception of move */
7605             /* and interpreting offer as claim if it brings draw condition */
7606             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7607                 SendToProgram("draw\n", cps->other);
7608             }
7609             if (cps->other->sendTime) {
7610                 SendTimeRemaining(cps->other,
7611                                   cps->other->twoMachinesColor[0] == 'w');
7612             }
7613             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7614             if (firstMove && !bookHit) {
7615                 firstMove = FALSE;
7616                 if (cps->other->useColors) {
7617                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7618                 }
7619                 SendToProgram("go\n", cps->other);
7620             }
7621             cps->other->maybeThinking = TRUE;
7622         }
7623
7624         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7625
7626         if (!pausing && appData.ringBellAfterMoves) {
7627             RingBell();
7628         }
7629
7630         /*
7631          * Reenable menu items that were disabled while
7632          * machine was thinking
7633          */
7634         if (gameMode != TwoMachinesPlay)
7635             SetUserThinkingEnables();
7636
7637         // [HGM] book: after book hit opponent has received move and is now in force mode
7638         // force the book reply into it, and then fake that it outputted this move by jumping
7639         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7640         if(bookHit) {
7641                 static char bookMove[MSG_SIZ]; // a bit generous?
7642
7643                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7644                 strcat(bookMove, bookHit);
7645                 message = bookMove;
7646                 cps = cps->other;
7647                 programStats.nodes = programStats.depth = programStats.time =
7648                 programStats.score = programStats.got_only_move = 0;
7649                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7650
7651                 if(cps->lastPing != cps->lastPong) {
7652                     savedMessage = message; // args for deferred call
7653                     savedState = cps;
7654                     ScheduleDelayedEvent(DeferredBookMove, 10);
7655                     return;
7656                 }
7657                 goto FakeBookMove;
7658         }
7659
7660         return;
7661     }
7662
7663     /* Set special modes for chess engines.  Later something general
7664      *  could be added here; for now there is just one kludge feature,
7665      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7666      *  when "xboard" is given as an interactive command.
7667      */
7668     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7669         cps->useSigint = FALSE;
7670         cps->useSigterm = FALSE;
7671     }
7672     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7673       ParseFeatures(message+8, cps);
7674       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7675     }
7676
7677     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7678       int dummy, s=6; char buf[MSG_SIZ];
7679       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7680       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7681       ParseFEN(boards[0], &dummy, message+s);
7682       DrawPosition(TRUE, boards[0]);
7683       startedFromSetupPosition = TRUE;
7684       return;
7685     }
7686     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7687      * want this, I was asked to put it in, and obliged.
7688      */
7689     if (!strncmp(message, "setboard ", 9)) {
7690         Board initial_position;
7691
7692         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7693
7694         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7695             DisplayError(_("Bad FEN received from engine"), 0);
7696             return ;
7697         } else {
7698            Reset(TRUE, FALSE);
7699            CopyBoard(boards[0], initial_position);
7700            initialRulePlies = FENrulePlies;
7701            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7702            else gameMode = MachinePlaysBlack;
7703            DrawPosition(FALSE, boards[currentMove]);
7704         }
7705         return;
7706     }
7707
7708     /*
7709      * Look for communication commands
7710      */
7711     if (!strncmp(message, "telluser ", 9)) {
7712         if(message[9] == '\\' && message[10] == '\\')
7713             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7714         DisplayNote(message + 9);
7715         return;
7716     }
7717     if (!strncmp(message, "tellusererror ", 14)) {
7718         cps->userError = 1;
7719         if(message[14] == '\\' && message[15] == '\\')
7720             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7721         DisplayError(message + 14, 0);
7722         return;
7723     }
7724     if (!strncmp(message, "tellopponent ", 13)) {
7725       if (appData.icsActive) {
7726         if (loggedOn) {
7727           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7728           SendToICS(buf1);
7729         }
7730       } else {
7731         DisplayNote(message + 13);
7732       }
7733       return;
7734     }
7735     if (!strncmp(message, "tellothers ", 11)) {
7736       if (appData.icsActive) {
7737         if (loggedOn) {
7738           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7739           SendToICS(buf1);
7740         }
7741       }
7742       return;
7743     }
7744     if (!strncmp(message, "tellall ", 8)) {
7745       if (appData.icsActive) {
7746         if (loggedOn) {
7747           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7748           SendToICS(buf1);
7749         }
7750       } else {
7751         DisplayNote(message + 8);
7752       }
7753       return;
7754     }
7755     if (strncmp(message, "warning", 7) == 0) {
7756         /* Undocumented feature, use tellusererror in new code */
7757         DisplayError(message, 0);
7758         return;
7759     }
7760     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7761         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7762         strcat(realname, " query");
7763         AskQuestion(realname, buf2, buf1, cps->pr);
7764         return;
7765     }
7766     /* Commands from the engine directly to ICS.  We don't allow these to be
7767      *  sent until we are logged on. Crafty kibitzes have been known to
7768      *  interfere with the login process.
7769      */
7770     if (loggedOn) {
7771         if (!strncmp(message, "tellics ", 8)) {
7772             SendToICS(message + 8);
7773             SendToICS("\n");
7774             return;
7775         }
7776         if (!strncmp(message, "tellicsnoalias ", 15)) {
7777             SendToICS(ics_prefix);
7778             SendToICS(message + 15);
7779             SendToICS("\n");
7780             return;
7781         }
7782         /* The following are for backward compatibility only */
7783         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7784             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7785             SendToICS(ics_prefix);
7786             SendToICS(message);
7787             SendToICS("\n");
7788             return;
7789         }
7790     }
7791     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7792         return;
7793     }
7794     /*
7795      * If the move is illegal, cancel it and redraw the board.
7796      * Also deal with other error cases.  Matching is rather loose
7797      * here to accommodate engines written before the spec.
7798      */
7799     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7800         strncmp(message, "Error", 5) == 0) {
7801         if (StrStr(message, "name") ||
7802             StrStr(message, "rating") || StrStr(message, "?") ||
7803             StrStr(message, "result") || StrStr(message, "board") ||
7804             StrStr(message, "bk") || StrStr(message, "computer") ||
7805             StrStr(message, "variant") || StrStr(message, "hint") ||
7806             StrStr(message, "random") || StrStr(message, "depth") ||
7807             StrStr(message, "accepted")) {
7808             return;
7809         }
7810         if (StrStr(message, "protover")) {
7811           /* Program is responding to input, so it's apparently done
7812              initializing, and this error message indicates it is
7813              protocol version 1.  So we don't need to wait any longer
7814              for it to initialize and send feature commands. */
7815           FeatureDone(cps, 1);
7816           cps->protocolVersion = 1;
7817           return;
7818         }
7819         cps->maybeThinking = FALSE;
7820
7821         if (StrStr(message, "draw")) {
7822             /* Program doesn't have "draw" command */
7823             cps->sendDrawOffers = 0;
7824             return;
7825         }
7826         if (cps->sendTime != 1 &&
7827             (StrStr(message, "time") || StrStr(message, "otim"))) {
7828           /* Program apparently doesn't have "time" or "otim" command */
7829           cps->sendTime = 0;
7830           return;
7831         }
7832         if (StrStr(message, "analyze")) {
7833             cps->analysisSupport = FALSE;
7834             cps->analyzing = FALSE;
7835             Reset(FALSE, TRUE);
7836             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7837             DisplayError(buf2, 0);
7838             return;
7839         }
7840         if (StrStr(message, "(no matching move)st")) {
7841           /* Special kludge for GNU Chess 4 only */
7842           cps->stKludge = TRUE;
7843           SendTimeControl(cps, movesPerSession, timeControl,
7844                           timeIncrement, appData.searchDepth,
7845                           searchTime);
7846           return;
7847         }
7848         if (StrStr(message, "(no matching move)sd")) {
7849           /* Special kludge for GNU Chess 4 only */
7850           cps->sdKludge = TRUE;
7851           SendTimeControl(cps, movesPerSession, timeControl,
7852                           timeIncrement, appData.searchDepth,
7853                           searchTime);
7854           return;
7855         }
7856         if (!StrStr(message, "llegal")) {
7857             return;
7858         }
7859         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7860             gameMode == IcsIdle) return;
7861         if (forwardMostMove <= backwardMostMove) return;
7862         if (pausing) PauseEvent();
7863       if(appData.forceIllegal) {
7864             // [HGM] illegal: machine refused move; force position after move into it
7865           SendToProgram("force\n", cps);
7866           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7867                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7868                 // when black is to move, while there might be nothing on a2 or black
7869                 // might already have the move. So send the board as if white has the move.
7870                 // But first we must change the stm of the engine, as it refused the last move
7871                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7872                 if(WhiteOnMove(forwardMostMove)) {
7873                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7874                     SendBoard(cps, forwardMostMove); // kludgeless board
7875                 } else {
7876                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7877                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7878                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7879                 }
7880           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7881             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7882                  gameMode == TwoMachinesPlay)
7883               SendToProgram("go\n", cps);
7884             return;
7885       } else
7886         if (gameMode == PlayFromGameFile) {
7887             /* Stop reading this game file */
7888             gameMode = EditGame;
7889             ModeHighlight();
7890         }
7891         /* [HGM] illegal-move claim should forfeit game when Xboard */
7892         /* only passes fully legal moves                            */
7893         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7894             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7895                                 "False illegal-move claim", GE_XBOARD );
7896             return; // do not take back move we tested as valid
7897         }
7898         currentMove = forwardMostMove-1;
7899         DisplayMove(currentMove-1); /* before DisplayMoveError */
7900         SwitchClocks(forwardMostMove-1); // [HGM] race
7901         DisplayBothClocks();
7902         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7903                 parseList[currentMove], _(cps->which));
7904         DisplayMoveError(buf1);
7905         DrawPosition(FALSE, boards[currentMove]);
7906         return;
7907     }
7908     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7909         /* Program has a broken "time" command that
7910            outputs a string not ending in newline.
7911            Don't use it. */
7912         cps->sendTime = 0;
7913     }
7914
7915     /*
7916      * If chess program startup fails, exit with an error message.
7917      * Attempts to recover here are futile.
7918      */
7919     if ((StrStr(message, "unknown host") != NULL)
7920         || (StrStr(message, "No remote directory") != NULL)
7921         || (StrStr(message, "not found") != NULL)
7922         || (StrStr(message, "No such file") != NULL)
7923         || (StrStr(message, "can't alloc") != NULL)
7924         || (StrStr(message, "Permission denied") != NULL)) {
7925
7926         cps->maybeThinking = FALSE;
7927         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7928                 _(cps->which), cps->program, cps->host, message);
7929         RemoveInputSource(cps->isr);
7930         DisplayFatalError(buf1, 0, 1);
7931         return;
7932     }
7933
7934     /*
7935      * Look for hint output
7936      */
7937     if (sscanf(message, "Hint: %s", buf1) == 1) {
7938         if (cps == &first && hintRequested) {
7939             hintRequested = FALSE;
7940             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7941                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7942                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7943                                     PosFlags(forwardMostMove),
7944                                     fromY, fromX, toY, toX, promoChar, buf1);
7945                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7946                 DisplayInformation(buf2);
7947             } else {
7948                 /* Hint move could not be parsed!? */
7949               snprintf(buf2, sizeof(buf2),
7950                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7951                         buf1, _(cps->which));
7952                 DisplayError(buf2, 0);
7953             }
7954         } else {
7955           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7956         }
7957         return;
7958     }
7959
7960     /*
7961      * Ignore other messages if game is not in progress
7962      */
7963     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7964         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7965
7966     /*
7967      * look for win, lose, draw, or draw offer
7968      */
7969     if (strncmp(message, "1-0", 3) == 0) {
7970         char *p, *q, *r = "";
7971         p = strchr(message, '{');
7972         if (p) {
7973             q = strchr(p, '}');
7974             if (q) {
7975                 *q = NULLCHAR;
7976                 r = p + 1;
7977             }
7978         }
7979         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7980         return;
7981     } else if (strncmp(message, "0-1", 3) == 0) {
7982         char *p, *q, *r = "";
7983         p = strchr(message, '{');
7984         if (p) {
7985             q = strchr(p, '}');
7986             if (q) {
7987                 *q = NULLCHAR;
7988                 r = p + 1;
7989             }
7990         }
7991         /* Kludge for Arasan 4.1 bug */
7992         if (strcmp(r, "Black resigns") == 0) {
7993             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7994             return;
7995         }
7996         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7997         return;
7998     } else if (strncmp(message, "1/2", 3) == 0) {
7999         char *p, *q, *r = "";
8000         p = strchr(message, '{');
8001         if (p) {
8002             q = strchr(p, '}');
8003             if (q) {
8004                 *q = NULLCHAR;
8005                 r = p + 1;
8006             }
8007         }
8008
8009         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8010         return;
8011
8012     } else if (strncmp(message, "White resign", 12) == 0) {
8013         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8014         return;
8015     } else if (strncmp(message, "Black resign", 12) == 0) {
8016         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8017         return;
8018     } else if (strncmp(message, "White matches", 13) == 0 ||
8019                strncmp(message, "Black matches", 13) == 0   ) {
8020         /* [HGM] ignore GNUShogi noises */
8021         return;
8022     } else if (strncmp(message, "White", 5) == 0 &&
8023                message[5] != '(' &&
8024                StrStr(message, "Black") == NULL) {
8025         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8026         return;
8027     } else if (strncmp(message, "Black", 5) == 0 &&
8028                message[5] != '(') {
8029         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8030         return;
8031     } else if (strcmp(message, "resign") == 0 ||
8032                strcmp(message, "computer resigns") == 0) {
8033         switch (gameMode) {
8034           case MachinePlaysBlack:
8035           case IcsPlayingBlack:
8036             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8037             break;
8038           case MachinePlaysWhite:
8039           case IcsPlayingWhite:
8040             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8041             break;
8042           case TwoMachinesPlay:
8043             if (cps->twoMachinesColor[0] == 'w')
8044               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8045             else
8046               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8047             break;
8048           default:
8049             /* can't happen */
8050             break;
8051         }
8052         return;
8053     } else if (strncmp(message, "opponent mates", 14) == 0) {
8054         switch (gameMode) {
8055           case MachinePlaysBlack:
8056           case IcsPlayingBlack:
8057             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8058             break;
8059           case MachinePlaysWhite:
8060           case IcsPlayingWhite:
8061             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8062             break;
8063           case TwoMachinesPlay:
8064             if (cps->twoMachinesColor[0] == 'w')
8065               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8066             else
8067               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8068             break;
8069           default:
8070             /* can't happen */
8071             break;
8072         }
8073         return;
8074     } else if (strncmp(message, "computer mates", 14) == 0) {
8075         switch (gameMode) {
8076           case MachinePlaysBlack:
8077           case IcsPlayingBlack:
8078             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8079             break;
8080           case MachinePlaysWhite:
8081           case IcsPlayingWhite:
8082             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8083             break;
8084           case TwoMachinesPlay:
8085             if (cps->twoMachinesColor[0] == 'w')
8086               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8087             else
8088               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8089             break;
8090           default:
8091             /* can't happen */
8092             break;
8093         }
8094         return;
8095     } else if (strncmp(message, "checkmate", 9) == 0) {
8096         if (WhiteOnMove(forwardMostMove)) {
8097             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8098         } else {
8099             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8100         }
8101         return;
8102     } else if (strstr(message, "Draw") != NULL ||
8103                strstr(message, "game is a draw") != NULL) {
8104         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8105         return;
8106     } else if (strstr(message, "offer") != NULL &&
8107                strstr(message, "draw") != NULL) {
8108 #if ZIPPY
8109         if (appData.zippyPlay && first.initDone) {
8110             /* Relay offer to ICS */
8111             SendToICS(ics_prefix);
8112             SendToICS("draw\n");
8113         }
8114 #endif
8115         cps->offeredDraw = 2; /* valid until this engine moves twice */
8116         if (gameMode == TwoMachinesPlay) {
8117             if (cps->other->offeredDraw) {
8118                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8119             /* [HGM] in two-machine mode we delay relaying draw offer      */
8120             /* until after we also have move, to see if it is really claim */
8121             }
8122         } else if (gameMode == MachinePlaysWhite ||
8123                    gameMode == MachinePlaysBlack) {
8124           if (userOfferedDraw) {
8125             DisplayInformation(_("Machine accepts your draw offer"));
8126             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8127           } else {
8128             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8129           }
8130         }
8131     }
8132
8133
8134     /*
8135      * Look for thinking output
8136      */
8137     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8138           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8139                                 ) {
8140         int plylev, mvleft, mvtot, curscore, time;
8141         char mvname[MOVE_LEN];
8142         u64 nodes; // [DM]
8143         char plyext;
8144         int ignore = FALSE;
8145         int prefixHint = FALSE;
8146         mvname[0] = NULLCHAR;
8147
8148         switch (gameMode) {
8149           case MachinePlaysBlack:
8150           case IcsPlayingBlack:
8151             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8152             break;
8153           case MachinePlaysWhite:
8154           case IcsPlayingWhite:
8155             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8156             break;
8157           case AnalyzeMode:
8158           case AnalyzeFile:
8159             break;
8160           case IcsObserving: /* [DM] icsEngineAnalyze */
8161             if (!appData.icsEngineAnalyze) ignore = TRUE;
8162             break;
8163           case TwoMachinesPlay:
8164             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8165                 ignore = TRUE;
8166             }
8167             break;
8168           default:
8169             ignore = TRUE;
8170             break;
8171         }
8172
8173         if (!ignore) {
8174             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8175             buf1[0] = NULLCHAR;
8176             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8177                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8178
8179                 if (plyext != ' ' && plyext != '\t') {
8180                     time *= 100;
8181                 }
8182
8183                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8184                 if( cps->scoreIsAbsolute &&
8185                     ( gameMode == MachinePlaysBlack ||
8186                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8187                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8188                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8189                      !WhiteOnMove(currentMove)
8190                     ) )
8191                 {
8192                     curscore = -curscore;
8193                 }
8194
8195
8196                 tempStats.depth = plylev;
8197                 tempStats.nodes = nodes;
8198                 tempStats.time = time;
8199                 tempStats.score = curscore;
8200                 tempStats.got_only_move = 0;
8201
8202                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8203                         int ticklen;
8204
8205                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8206                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8207                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8208                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8209                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8210                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8211                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8212                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8213                 }
8214
8215                 /* Buffer overflow protection */
8216                 if (buf1[0] != NULLCHAR) {
8217                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8218                         && appData.debugMode) {
8219                         fprintf(debugFP,
8220                                 "PV is too long; using the first %u bytes.\n",
8221                                 (unsigned) sizeof(tempStats.movelist) - 1);
8222                     }
8223
8224                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8225                 } else {
8226                     sprintf(tempStats.movelist, " no PV\n");
8227                 }
8228
8229                 if (tempStats.seen_stat) {
8230                     tempStats.ok_to_send = 1;
8231                 }
8232
8233                 if (strchr(tempStats.movelist, '(') != NULL) {
8234                     tempStats.line_is_book = 1;
8235                     tempStats.nr_moves = 0;
8236                     tempStats.moves_left = 0;
8237                 } else {
8238                     tempStats.line_is_book = 0;
8239                 }
8240
8241                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8242                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8243
8244                 SendProgramStatsToFrontend( cps, &tempStats );
8245
8246                 /*
8247                     [AS] Protect the thinkOutput buffer from overflow... this
8248                     is only useful if buf1 hasn't overflowed first!
8249                 */
8250                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8251                          plylev,
8252                          (gameMode == TwoMachinesPlay ?
8253                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8254                          ((double) curscore) / 100.0,
8255                          prefixHint ? lastHint : "",
8256                          prefixHint ? " " : "" );
8257
8258                 if( buf1[0] != NULLCHAR ) {
8259                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8260
8261                     if( strlen(buf1) > max_len ) {
8262                         if( appData.debugMode) {
8263                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8264                         }
8265                         buf1[max_len+1] = '\0';
8266                     }
8267
8268                     strcat( thinkOutput, buf1 );
8269                 }
8270
8271                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8272                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8273                     DisplayMove(currentMove - 1);
8274                 }
8275                 return;
8276
8277             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8278                 /* crafty (9.25+) says "(only move) <move>"
8279                  * if there is only 1 legal move
8280                  */
8281                 sscanf(p, "(only move) %s", buf1);
8282                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8283                 sprintf(programStats.movelist, "%s (only move)", buf1);
8284                 programStats.depth = 1;
8285                 programStats.nr_moves = 1;
8286                 programStats.moves_left = 1;
8287                 programStats.nodes = 1;
8288                 programStats.time = 1;
8289                 programStats.got_only_move = 1;
8290
8291                 /* Not really, but we also use this member to
8292                    mean "line isn't going to change" (Crafty
8293                    isn't searching, so stats won't change) */
8294                 programStats.line_is_book = 1;
8295
8296                 SendProgramStatsToFrontend( cps, &programStats );
8297
8298                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8299                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8300                     DisplayMove(currentMove - 1);
8301                 }
8302                 return;
8303             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8304                               &time, &nodes, &plylev, &mvleft,
8305                               &mvtot, mvname) >= 5) {
8306                 /* The stat01: line is from Crafty (9.29+) in response
8307                    to the "." command */
8308                 programStats.seen_stat = 1;
8309                 cps->maybeThinking = TRUE;
8310
8311                 if (programStats.got_only_move || !appData.periodicUpdates)
8312                   return;
8313
8314                 programStats.depth = plylev;
8315                 programStats.time = time;
8316                 programStats.nodes = nodes;
8317                 programStats.moves_left = mvleft;
8318                 programStats.nr_moves = mvtot;
8319                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8320                 programStats.ok_to_send = 1;
8321                 programStats.movelist[0] = '\0';
8322
8323                 SendProgramStatsToFrontend( cps, &programStats );
8324
8325                 return;
8326
8327             } else if (strncmp(message,"++",2) == 0) {
8328                 /* Crafty 9.29+ outputs this */
8329                 programStats.got_fail = 2;
8330                 return;
8331
8332             } else if (strncmp(message,"--",2) == 0) {
8333                 /* Crafty 9.29+ outputs this */
8334                 programStats.got_fail = 1;
8335                 return;
8336
8337             } else if (thinkOutput[0] != NULLCHAR &&
8338                        strncmp(message, "    ", 4) == 0) {
8339                 unsigned message_len;
8340
8341                 p = message;
8342                 while (*p && *p == ' ') p++;
8343
8344                 message_len = strlen( p );
8345
8346                 /* [AS] Avoid buffer overflow */
8347                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8348                     strcat(thinkOutput, " ");
8349                     strcat(thinkOutput, p);
8350                 }
8351
8352                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8353                     strcat(programStats.movelist, " ");
8354                     strcat(programStats.movelist, p);
8355                 }
8356
8357                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8358                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8359                     DisplayMove(currentMove - 1);
8360                 }
8361                 return;
8362             }
8363         }
8364         else {
8365             buf1[0] = NULLCHAR;
8366
8367             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8368                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8369             {
8370                 ChessProgramStats cpstats;
8371
8372                 if (plyext != ' ' && plyext != '\t') {
8373                     time *= 100;
8374                 }
8375
8376                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8377                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8378                     curscore = -curscore;
8379                 }
8380
8381                 cpstats.depth = plylev;
8382                 cpstats.nodes = nodes;
8383                 cpstats.time = time;
8384                 cpstats.score = curscore;
8385                 cpstats.got_only_move = 0;
8386                 cpstats.movelist[0] = '\0';
8387
8388                 if (buf1[0] != NULLCHAR) {
8389                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8390                 }
8391
8392                 cpstats.ok_to_send = 0;
8393                 cpstats.line_is_book = 0;
8394                 cpstats.nr_moves = 0;
8395                 cpstats.moves_left = 0;
8396
8397                 SendProgramStatsToFrontend( cps, &cpstats );
8398             }
8399         }
8400     }
8401 }
8402
8403
8404 /* Parse a game score from the character string "game", and
8405    record it as the history of the current game.  The game
8406    score is NOT assumed to start from the standard position.
8407    The display is not updated in any way.
8408    */
8409 void
8410 ParseGameHistory(game)
8411      char *game;
8412 {
8413     ChessMove moveType;
8414     int fromX, fromY, toX, toY, boardIndex;
8415     char promoChar;
8416     char *p, *q;
8417     char buf[MSG_SIZ];
8418
8419     if (appData.debugMode)
8420       fprintf(debugFP, "Parsing game history: %s\n", game);
8421
8422     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8423     gameInfo.site = StrSave(appData.icsHost);
8424     gameInfo.date = PGNDate();
8425     gameInfo.round = StrSave("-");
8426
8427     /* Parse out names of players */
8428     while (*game == ' ') game++;
8429     p = buf;
8430     while (*game != ' ') *p++ = *game++;
8431     *p = NULLCHAR;
8432     gameInfo.white = StrSave(buf);
8433     while (*game == ' ') game++;
8434     p = buf;
8435     while (*game != ' ' && *game != '\n') *p++ = *game++;
8436     *p = NULLCHAR;
8437     gameInfo.black = StrSave(buf);
8438
8439     /* Parse moves */
8440     boardIndex = blackPlaysFirst ? 1 : 0;
8441     yynewstr(game);
8442     for (;;) {
8443         yyboardindex = boardIndex;
8444         moveType = (ChessMove) Myylex();
8445         switch (moveType) {
8446           case IllegalMove:             /* maybe suicide chess, etc. */
8447   if (appData.debugMode) {
8448     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8449     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8450     setbuf(debugFP, NULL);
8451   }
8452           case WhitePromotion:
8453           case BlackPromotion:
8454           case WhiteNonPromotion:
8455           case BlackNonPromotion:
8456           case NormalMove:
8457           case WhiteCapturesEnPassant:
8458           case BlackCapturesEnPassant:
8459           case WhiteKingSideCastle:
8460           case WhiteQueenSideCastle:
8461           case BlackKingSideCastle:
8462           case BlackQueenSideCastle:
8463           case WhiteKingSideCastleWild:
8464           case WhiteQueenSideCastleWild:
8465           case BlackKingSideCastleWild:
8466           case BlackQueenSideCastleWild:
8467           /* PUSH Fabien */
8468           case WhiteHSideCastleFR:
8469           case WhiteASideCastleFR:
8470           case BlackHSideCastleFR:
8471           case BlackASideCastleFR:
8472           /* POP Fabien */
8473             fromX = currentMoveString[0] - AAA;
8474             fromY = currentMoveString[1] - ONE;
8475             toX = currentMoveString[2] - AAA;
8476             toY = currentMoveString[3] - ONE;
8477             promoChar = currentMoveString[4];
8478             break;
8479           case WhiteDrop:
8480           case BlackDrop:
8481             fromX = moveType == WhiteDrop ?
8482               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8483             (int) CharToPiece(ToLower(currentMoveString[0]));
8484             fromY = DROP_RANK;
8485             toX = currentMoveString[2] - AAA;
8486             toY = currentMoveString[3] - ONE;
8487             promoChar = NULLCHAR;
8488             break;
8489           case AmbiguousMove:
8490             /* bug? */
8491             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8492   if (appData.debugMode) {
8493     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8494     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8495     setbuf(debugFP, NULL);
8496   }
8497             DisplayError(buf, 0);
8498             return;
8499           case ImpossibleMove:
8500             /* bug? */
8501             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8502   if (appData.debugMode) {
8503     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8504     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8505     setbuf(debugFP, NULL);
8506   }
8507             DisplayError(buf, 0);
8508             return;
8509           case EndOfFile:
8510             if (boardIndex < backwardMostMove) {
8511                 /* Oops, gap.  How did that happen? */
8512                 DisplayError(_("Gap in move list"), 0);
8513                 return;
8514             }
8515             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8516             if (boardIndex > forwardMostMove) {
8517                 forwardMostMove = boardIndex;
8518             }
8519             return;
8520           case ElapsedTime:
8521             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8522                 strcat(parseList[boardIndex-1], " ");
8523                 strcat(parseList[boardIndex-1], yy_text);
8524             }
8525             continue;
8526           case Comment:
8527           case PGNTag:
8528           case NAG:
8529           default:
8530             /* ignore */
8531             continue;
8532           case WhiteWins:
8533           case BlackWins:
8534           case GameIsDrawn:
8535           case GameUnfinished:
8536             if (gameMode == IcsExamining) {
8537                 if (boardIndex < backwardMostMove) {
8538                     /* Oops, gap.  How did that happen? */
8539                     return;
8540                 }
8541                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8542                 return;
8543             }
8544             gameInfo.result = moveType;
8545             p = strchr(yy_text, '{');
8546             if (p == NULL) p = strchr(yy_text, '(');
8547             if (p == NULL) {
8548                 p = yy_text;
8549                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8550             } else {
8551                 q = strchr(p, *p == '{' ? '}' : ')');
8552                 if (q != NULL) *q = NULLCHAR;
8553                 p++;
8554             }
8555             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8556             gameInfo.resultDetails = StrSave(p);
8557             continue;
8558         }
8559         if (boardIndex >= forwardMostMove &&
8560             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8561             backwardMostMove = blackPlaysFirst ? 1 : 0;
8562             return;
8563         }
8564         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8565                                  fromY, fromX, toY, toX, promoChar,
8566                                  parseList[boardIndex]);
8567         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8568         /* currentMoveString is set as a side-effect of yylex */
8569         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8570         strcat(moveList[boardIndex], "\n");
8571         boardIndex++;
8572         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8573         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8574           case MT_NONE:
8575           case MT_STALEMATE:
8576           default:
8577             break;
8578           case MT_CHECK:
8579             if(gameInfo.variant != VariantShogi)
8580                 strcat(parseList[boardIndex - 1], "+");
8581             break;
8582           case MT_CHECKMATE:
8583           case MT_STAINMATE:
8584             strcat(parseList[boardIndex - 1], "#");
8585             break;
8586         }
8587     }
8588 }
8589
8590
8591 /* Apply a move to the given board  */
8592 void
8593 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8594      int fromX, fromY, toX, toY;
8595      int promoChar;
8596      Board board;
8597 {
8598   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8599   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8600
8601     /* [HGM] compute & store e.p. status and castling rights for new position */
8602     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8603
8604       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8605       oldEP = (signed char)board[EP_STATUS];
8606       board[EP_STATUS] = EP_NONE;
8607
8608       if( board[toY][toX] != EmptySquare )
8609            board[EP_STATUS] = EP_CAPTURE;
8610
8611   if (fromY == DROP_RANK) {
8612         /* must be first */
8613         piece = board[toY][toX] = (ChessSquare) fromX;
8614   } else {
8615       int i;
8616
8617       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8618            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8619                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8620       } else
8621       if( board[fromY][fromX] == WhitePawn ) {
8622            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8623                board[EP_STATUS] = EP_PAWN_MOVE;
8624            if( toY-fromY==2) {
8625                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8626                         gameInfo.variant != VariantBerolina || toX < fromX)
8627                       board[EP_STATUS] = toX | berolina;
8628                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8629                         gameInfo.variant != VariantBerolina || toX > fromX)
8630                       board[EP_STATUS] = toX;
8631            }
8632       } else
8633       if( board[fromY][fromX] == BlackPawn ) {
8634            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8635                board[EP_STATUS] = EP_PAWN_MOVE;
8636            if( toY-fromY== -2) {
8637                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8638                         gameInfo.variant != VariantBerolina || toX < fromX)
8639                       board[EP_STATUS] = toX | berolina;
8640                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8641                         gameInfo.variant != VariantBerolina || toX > fromX)
8642                       board[EP_STATUS] = toX;
8643            }
8644        }
8645
8646        for(i=0; i<nrCastlingRights; i++) {
8647            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8648               board[CASTLING][i] == toX   && castlingRank[i] == toY
8649              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8650        }
8651
8652      if (fromX == toX && fromY == toY) return;
8653
8654      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8655      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8656      if(gameInfo.variant == VariantKnightmate)
8657          king += (int) WhiteUnicorn - (int) WhiteKing;
8658
8659     /* Code added by Tord: */
8660     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8661     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8662         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8663       board[fromY][fromX] = EmptySquare;
8664       board[toY][toX] = EmptySquare;
8665       if((toX > fromX) != (piece == WhiteRook)) {
8666         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8667       } else {
8668         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8669       }
8670     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8671                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8672       board[fromY][fromX] = EmptySquare;
8673       board[toY][toX] = EmptySquare;
8674       if((toX > fromX) != (piece == BlackRook)) {
8675         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8676       } else {
8677         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8678       }
8679     /* End of code added by Tord */
8680
8681     } else if (board[fromY][fromX] == king
8682         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8683         && toY == fromY && toX > fromX+1) {
8684         board[fromY][fromX] = EmptySquare;
8685         board[toY][toX] = king;
8686         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8687         board[fromY][BOARD_RGHT-1] = EmptySquare;
8688     } else if (board[fromY][fromX] == king
8689         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8690                && toY == fromY && toX < fromX-1) {
8691         board[fromY][fromX] = EmptySquare;
8692         board[toY][toX] = king;
8693         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8694         board[fromY][BOARD_LEFT] = EmptySquare;
8695     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8696                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8697                && toY >= BOARD_HEIGHT-promoRank
8698                ) {
8699         /* white pawn promotion */
8700         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8701         if (board[toY][toX] == EmptySquare) {
8702             board[toY][toX] = WhiteQueen;
8703         }
8704         if(gameInfo.variant==VariantBughouse ||
8705            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8706             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8707         board[fromY][fromX] = EmptySquare;
8708     } else if ((fromY == BOARD_HEIGHT-4)
8709                && (toX != fromX)
8710                && gameInfo.variant != VariantXiangqi
8711                && gameInfo.variant != VariantBerolina
8712                && (board[fromY][fromX] == WhitePawn)
8713                && (board[toY][toX] == EmptySquare)) {
8714         board[fromY][fromX] = EmptySquare;
8715         board[toY][toX] = WhitePawn;
8716         captured = board[toY - 1][toX];
8717         board[toY - 1][toX] = EmptySquare;
8718     } else if ((fromY == BOARD_HEIGHT-4)
8719                && (toX == fromX)
8720                && gameInfo.variant == VariantBerolina
8721                && (board[fromY][fromX] == WhitePawn)
8722                && (board[toY][toX] == EmptySquare)) {
8723         board[fromY][fromX] = EmptySquare;
8724         board[toY][toX] = WhitePawn;
8725         if(oldEP & EP_BEROLIN_A) {
8726                 captured = board[fromY][fromX-1];
8727                 board[fromY][fromX-1] = EmptySquare;
8728         }else{  captured = board[fromY][fromX+1];
8729                 board[fromY][fromX+1] = EmptySquare;
8730         }
8731     } else if (board[fromY][fromX] == king
8732         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8733                && toY == fromY && toX > fromX+1) {
8734         board[fromY][fromX] = EmptySquare;
8735         board[toY][toX] = king;
8736         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8737         board[fromY][BOARD_RGHT-1] = EmptySquare;
8738     } else if (board[fromY][fromX] == king
8739         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8740                && toY == fromY && toX < fromX-1) {
8741         board[fromY][fromX] = EmptySquare;
8742         board[toY][toX] = king;
8743         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8744         board[fromY][BOARD_LEFT] = EmptySquare;
8745     } else if (fromY == 7 && fromX == 3
8746                && board[fromY][fromX] == BlackKing
8747                && toY == 7 && toX == 5) {
8748         board[fromY][fromX] = EmptySquare;
8749         board[toY][toX] = BlackKing;
8750         board[fromY][7] = EmptySquare;
8751         board[toY][4] = BlackRook;
8752     } else if (fromY == 7 && fromX == 3
8753                && board[fromY][fromX] == BlackKing
8754                && toY == 7 && toX == 1) {
8755         board[fromY][fromX] = EmptySquare;
8756         board[toY][toX] = BlackKing;
8757         board[fromY][0] = EmptySquare;
8758         board[toY][2] = BlackRook;
8759     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8760                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8761                && toY < promoRank
8762                ) {
8763         /* black pawn promotion */
8764         board[toY][toX] = CharToPiece(ToLower(promoChar));
8765         if (board[toY][toX] == EmptySquare) {
8766             board[toY][toX] = BlackQueen;
8767         }
8768         if(gameInfo.variant==VariantBughouse ||
8769            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8770             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8771         board[fromY][fromX] = EmptySquare;
8772     } else if ((fromY == 3)
8773                && (toX != fromX)
8774                && gameInfo.variant != VariantXiangqi
8775                && gameInfo.variant != VariantBerolina
8776                && (board[fromY][fromX] == BlackPawn)
8777                && (board[toY][toX] == EmptySquare)) {
8778         board[fromY][fromX] = EmptySquare;
8779         board[toY][toX] = BlackPawn;
8780         captured = board[toY + 1][toX];
8781         board[toY + 1][toX] = EmptySquare;
8782     } else if ((fromY == 3)
8783                && (toX == fromX)
8784                && gameInfo.variant == VariantBerolina
8785                && (board[fromY][fromX] == BlackPawn)
8786                && (board[toY][toX] == EmptySquare)) {
8787         board[fromY][fromX] = EmptySquare;
8788         board[toY][toX] = BlackPawn;
8789         if(oldEP & EP_BEROLIN_A) {
8790                 captured = board[fromY][fromX-1];
8791                 board[fromY][fromX-1] = EmptySquare;
8792         }else{  captured = board[fromY][fromX+1];
8793                 board[fromY][fromX+1] = EmptySquare;
8794         }
8795     } else {
8796         board[toY][toX] = board[fromY][fromX];
8797         board[fromY][fromX] = EmptySquare;
8798     }
8799   }
8800
8801     if (gameInfo.holdingsWidth != 0) {
8802
8803       /* !!A lot more code needs to be written to support holdings  */
8804       /* [HGM] OK, so I have written it. Holdings are stored in the */
8805       /* penultimate board files, so they are automaticlly stored   */
8806       /* in the game history.                                       */
8807       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8808                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8809         /* Delete from holdings, by decreasing count */
8810         /* and erasing image if necessary            */
8811         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8812         if(p < (int) BlackPawn) { /* white drop */
8813              p -= (int)WhitePawn;
8814                  p = PieceToNumber((ChessSquare)p);
8815              if(p >= gameInfo.holdingsSize) p = 0;
8816              if(--board[p][BOARD_WIDTH-2] <= 0)
8817                   board[p][BOARD_WIDTH-1] = EmptySquare;
8818              if((int)board[p][BOARD_WIDTH-2] < 0)
8819                         board[p][BOARD_WIDTH-2] = 0;
8820         } else {                  /* black drop */
8821              p -= (int)BlackPawn;
8822                  p = PieceToNumber((ChessSquare)p);
8823              if(p >= gameInfo.holdingsSize) p = 0;
8824              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8825                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8826              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8827                         board[BOARD_HEIGHT-1-p][1] = 0;
8828         }
8829       }
8830       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8831           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8832         /* [HGM] holdings: Add to holdings, if holdings exist */
8833         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8834                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8835                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8836         }
8837         p = (int) captured;
8838         if (p >= (int) BlackPawn) {
8839           p -= (int)BlackPawn;
8840           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8841                   /* in Shogi restore piece to its original  first */
8842                   captured = (ChessSquare) (DEMOTED captured);
8843                   p = DEMOTED p;
8844           }
8845           p = PieceToNumber((ChessSquare)p);
8846           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8847           board[p][BOARD_WIDTH-2]++;
8848           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8849         } else {
8850           p -= (int)WhitePawn;
8851           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8852                   captured = (ChessSquare) (DEMOTED captured);
8853                   p = DEMOTED p;
8854           }
8855           p = PieceToNumber((ChessSquare)p);
8856           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8857           board[BOARD_HEIGHT-1-p][1]++;
8858           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8859         }
8860       }
8861     } else if (gameInfo.variant == VariantAtomic) {
8862       if (captured != EmptySquare) {
8863         int y, x;
8864         for (y = toY-1; y <= toY+1; y++) {
8865           for (x = toX-1; x <= toX+1; x++) {
8866             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8867                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8868               board[y][x] = EmptySquare;
8869             }
8870           }
8871         }
8872         board[toY][toX] = EmptySquare;
8873       }
8874     }
8875     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8876         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8877     } else
8878     if(promoChar == '+') {
8879         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8880         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8881     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8882         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8883     }
8884     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8885                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8886         // [HGM] superchess: take promotion piece out of holdings
8887         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8888         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8889             if(!--board[k][BOARD_WIDTH-2])
8890                 board[k][BOARD_WIDTH-1] = EmptySquare;
8891         } else {
8892             if(!--board[BOARD_HEIGHT-1-k][1])
8893                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8894         }
8895     }
8896
8897 }
8898
8899 /* Updates forwardMostMove */
8900 void
8901 MakeMove(fromX, fromY, toX, toY, promoChar)
8902      int fromX, fromY, toX, toY;
8903      int promoChar;
8904 {
8905 //    forwardMostMove++; // [HGM] bare: moved downstream
8906
8907     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8908         int timeLeft; static int lastLoadFlag=0; int king, piece;
8909         piece = boards[forwardMostMove][fromY][fromX];
8910         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8911         if(gameInfo.variant == VariantKnightmate)
8912             king += (int) WhiteUnicorn - (int) WhiteKing;
8913         if(forwardMostMove == 0) {
8914             if(blackPlaysFirst)
8915                 fprintf(serverMoves, "%s;", second.tidy);
8916             fprintf(serverMoves, "%s;", first.tidy);
8917             if(!blackPlaysFirst)
8918                 fprintf(serverMoves, "%s;", second.tidy);
8919         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8920         lastLoadFlag = loadFlag;
8921         // print base move
8922         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8923         // print castling suffix
8924         if( toY == fromY && piece == king ) {
8925             if(toX-fromX > 1)
8926                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8927             if(fromX-toX >1)
8928                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8929         }
8930         // e.p. suffix
8931         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8932              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8933              boards[forwardMostMove][toY][toX] == EmptySquare
8934              && fromX != toX && fromY != toY)
8935                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8936         // promotion suffix
8937         if(promoChar != NULLCHAR)
8938                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8939         if(!loadFlag) {
8940             fprintf(serverMoves, "/%d/%d",
8941                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8942             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8943             else                      timeLeft = blackTimeRemaining/1000;
8944             fprintf(serverMoves, "/%d", timeLeft);
8945         }
8946         fflush(serverMoves);
8947     }
8948
8949     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8950       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8951                         0, 1);
8952       return;
8953     }
8954     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8955     if (commentList[forwardMostMove+1] != NULL) {
8956         free(commentList[forwardMostMove+1]);
8957         commentList[forwardMostMove+1] = NULL;
8958     }
8959     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8960     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8961     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8962     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8963     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8964     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8965     gameInfo.result = GameUnfinished;
8966     if (gameInfo.resultDetails != NULL) {
8967         free(gameInfo.resultDetails);
8968         gameInfo.resultDetails = NULL;
8969     }
8970     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8971                               moveList[forwardMostMove - 1]);
8972     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8973                              PosFlags(forwardMostMove - 1),
8974                              fromY, fromX, toY, toX, promoChar,
8975                              parseList[forwardMostMove - 1]);
8976     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8977       case MT_NONE:
8978       case MT_STALEMATE:
8979       default:
8980         break;
8981       case MT_CHECK:
8982         if(gameInfo.variant != VariantShogi)
8983             strcat(parseList[forwardMostMove - 1], "+");
8984         break;
8985       case MT_CHECKMATE:
8986       case MT_STAINMATE:
8987         strcat(parseList[forwardMostMove - 1], "#");
8988         break;
8989     }
8990     if (appData.debugMode) {
8991         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8992     }
8993
8994 }
8995
8996 /* Updates currentMove if not pausing */
8997 void
8998 ShowMove(fromX, fromY, toX, toY)
8999 {
9000     int instant = (gameMode == PlayFromGameFile) ?
9001         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9002     if(appData.noGUI) return;
9003     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9004         if (!instant) {
9005             if (forwardMostMove == currentMove + 1) {
9006                 AnimateMove(boards[forwardMostMove - 1],
9007                             fromX, fromY, toX, toY);
9008             }
9009             if (appData.highlightLastMove) {
9010                 SetHighlights(fromX, fromY, toX, toY);
9011             }
9012         }
9013         currentMove = forwardMostMove;
9014     }
9015
9016     if (instant) return;
9017
9018     DisplayMove(currentMove - 1);
9019     DrawPosition(FALSE, boards[currentMove]);
9020     DisplayBothClocks();
9021     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9022 }
9023
9024 void SendEgtPath(ChessProgramState *cps)
9025 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9026         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9027
9028         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9029
9030         while(*p) {
9031             char c, *q = name+1, *r, *s;
9032
9033             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9034             while(*p && *p != ',') *q++ = *p++;
9035             *q++ = ':'; *q = 0;
9036             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9037                 strcmp(name, ",nalimov:") == 0 ) {
9038                 // take nalimov path from the menu-changeable option first, if it is defined
9039               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9040                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9041             } else
9042             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9043                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9044                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9045                 s = r = StrStr(s, ":") + 1; // beginning of path info
9046                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9047                 c = *r; *r = 0;             // temporarily null-terminate path info
9048                     *--q = 0;               // strip of trailig ':' from name
9049                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9050                 *r = c;
9051                 SendToProgram(buf,cps);     // send egtbpath command for this format
9052             }
9053             if(*p == ',') p++; // read away comma to position for next format name
9054         }
9055 }
9056
9057 void
9058 InitChessProgram(cps, setup)
9059      ChessProgramState *cps;
9060      int setup; /* [HGM] needed to setup FRC opening position */
9061 {
9062     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9063     if (appData.noChessProgram) return;
9064     hintRequested = FALSE;
9065     bookRequested = FALSE;
9066
9067     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9068     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9069     if(cps->memSize) { /* [HGM] memory */
9070       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9071         SendToProgram(buf, cps);
9072     }
9073     SendEgtPath(cps); /* [HGM] EGT */
9074     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9075       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9076         SendToProgram(buf, cps);
9077     }
9078
9079     SendToProgram(cps->initString, cps);
9080     if (gameInfo.variant != VariantNormal &&
9081         gameInfo.variant != VariantLoadable
9082         /* [HGM] also send variant if board size non-standard */
9083         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9084                                             ) {
9085       char *v = VariantName(gameInfo.variant);
9086       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9087         /* [HGM] in protocol 1 we have to assume all variants valid */
9088         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9089         DisplayFatalError(buf, 0, 1);
9090         return;
9091       }
9092
9093       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9094       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9095       if( gameInfo.variant == VariantXiangqi )
9096            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9097       if( gameInfo.variant == VariantShogi )
9098            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9099       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9100            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9101       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9102           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9103            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9104       if( gameInfo.variant == VariantCourier )
9105            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9106       if( gameInfo.variant == VariantSuper )
9107            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9108       if( gameInfo.variant == VariantGreat )
9109            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9110       if( gameInfo.variant == VariantSChess )
9111            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9112
9113       if(overruled) {
9114         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9115                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9116            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9117            if(StrStr(cps->variants, b) == NULL) {
9118                // specific sized variant not known, check if general sizing allowed
9119                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9120                    if(StrStr(cps->variants, "boardsize") == NULL) {
9121                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9122                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9123                        DisplayFatalError(buf, 0, 1);
9124                        return;
9125                    }
9126                    /* [HGM] here we really should compare with the maximum supported board size */
9127                }
9128            }
9129       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9130       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9131       SendToProgram(buf, cps);
9132     }
9133     currentlyInitializedVariant = gameInfo.variant;
9134
9135     /* [HGM] send opening position in FRC to first engine */
9136     if(setup) {
9137           SendToProgram("force\n", cps);
9138           SendBoard(cps, 0);
9139           /* engine is now in force mode! Set flag to wake it up after first move. */
9140           setboardSpoiledMachineBlack = 1;
9141     }
9142
9143     if (cps->sendICS) {
9144       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9145       SendToProgram(buf, cps);
9146     }
9147     cps->maybeThinking = FALSE;
9148     cps->offeredDraw = 0;
9149     if (!appData.icsActive) {
9150         SendTimeControl(cps, movesPerSession, timeControl,
9151                         timeIncrement, appData.searchDepth,
9152                         searchTime);
9153     }
9154     if (appData.showThinking
9155         // [HGM] thinking: four options require thinking output to be sent
9156         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9157                                 ) {
9158         SendToProgram("post\n", cps);
9159     }
9160     SendToProgram("hard\n", cps);
9161     if (!appData.ponderNextMove) {
9162         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9163            it without being sure what state we are in first.  "hard"
9164            is not a toggle, so that one is OK.
9165          */
9166         SendToProgram("easy\n", cps);
9167     }
9168     if (cps->usePing) {
9169       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9170       SendToProgram(buf, cps);
9171     }
9172     cps->initDone = TRUE;
9173 }
9174
9175
9176 void
9177 StartChessProgram(cps)
9178      ChessProgramState *cps;
9179 {
9180     char buf[MSG_SIZ];
9181     int err;
9182
9183     if (appData.noChessProgram) return;
9184     cps->initDone = FALSE;
9185
9186     if (strcmp(cps->host, "localhost") == 0) {
9187         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9188     } else if (*appData.remoteShell == NULLCHAR) {
9189         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9190     } else {
9191         if (*appData.remoteUser == NULLCHAR) {
9192           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9193                     cps->program);
9194         } else {
9195           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9196                     cps->host, appData.remoteUser, cps->program);
9197         }
9198         err = StartChildProcess(buf, "", &cps->pr);
9199     }
9200
9201     if (err != 0) {
9202       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9203         DisplayFatalError(buf, err, 1);
9204         cps->pr = NoProc;
9205         cps->isr = NULL;
9206         return;
9207     }
9208
9209     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9210     if (cps->protocolVersion > 1) {
9211       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9212       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9213       cps->comboCnt = 0;  //                and values of combo boxes
9214       SendToProgram(buf, cps);
9215     } else {
9216       SendToProgram("xboard\n", cps);
9217     }
9218 }
9219
9220
9221 void
9222 TwoMachinesEventIfReady P((void))
9223 {
9224   if (first.lastPing != first.lastPong) {
9225     DisplayMessage("", _("Waiting for first chess program"));
9226     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9227     return;
9228   }
9229   if (second.lastPing != second.lastPong) {
9230     DisplayMessage("", _("Waiting for second chess program"));
9231     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9232     return;
9233   }
9234   ThawUI();
9235   TwoMachinesEvent();
9236 }
9237
9238 void
9239 NextMatchGame P((void))
9240 {
9241     int index; /* [HGM] autoinc: step load index during match */
9242     Reset(FALSE, TRUE);
9243     if (*appData.loadGameFile != NULLCHAR) {
9244         index = appData.loadGameIndex;
9245         if(index < 0) { // [HGM] autoinc
9246             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9247             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9248         }
9249         LoadGameFromFile(appData.loadGameFile,
9250                          index,
9251                          appData.loadGameFile, FALSE);
9252     } else if (*appData.loadPositionFile != NULLCHAR) {
9253         index = appData.loadPositionIndex;
9254         if(index < 0) { // [HGM] autoinc
9255             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9256             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9257         }
9258         LoadPositionFromFile(appData.loadPositionFile,
9259                              index,
9260                              appData.loadPositionFile);
9261     }
9262     TwoMachinesEventIfReady();
9263 }
9264
9265 void UserAdjudicationEvent( int result )
9266 {
9267     ChessMove gameResult = GameIsDrawn;
9268
9269     if( result > 0 ) {
9270         gameResult = WhiteWins;
9271     }
9272     else if( result < 0 ) {
9273         gameResult = BlackWins;
9274     }
9275
9276     if( gameMode == TwoMachinesPlay ) {
9277         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9278     }
9279 }
9280
9281
9282 // [HGM] save: calculate checksum of game to make games easily identifiable
9283 int StringCheckSum(char *s)
9284 {
9285         int i = 0;
9286         if(s==NULL) return 0;
9287         while(*s) i = i*259 + *s++;
9288         return i;
9289 }
9290
9291 int GameCheckSum()
9292 {
9293         int i, sum=0;
9294         for(i=backwardMostMove; i<forwardMostMove; i++) {
9295                 sum += pvInfoList[i].depth;
9296                 sum += StringCheckSum(parseList[i]);
9297                 sum += StringCheckSum(commentList[i]);
9298                 sum *= 261;
9299         }
9300         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9301         return sum + StringCheckSum(commentList[i]);
9302 } // end of save patch
9303
9304 void
9305 GameEnds(result, resultDetails, whosays)
9306      ChessMove result;
9307      char *resultDetails;
9308      int whosays;
9309 {
9310     GameMode nextGameMode;
9311     int isIcsGame;
9312     char buf[MSG_SIZ], popupRequested = 0;
9313
9314     if(endingGame) return; /* [HGM] crash: forbid recursion */
9315     endingGame = 1;
9316     if(twoBoards) { // [HGM] dual: switch back to one board
9317         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9318         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9319     }
9320     if (appData.debugMode) {
9321       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9322               result, resultDetails ? resultDetails : "(null)", whosays);
9323     }
9324
9325     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9326
9327     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9328         /* If we are playing on ICS, the server decides when the
9329            game is over, but the engine can offer to draw, claim
9330            a draw, or resign.
9331          */
9332 #if ZIPPY
9333         if (appData.zippyPlay && first.initDone) {
9334             if (result == GameIsDrawn) {
9335                 /* In case draw still needs to be claimed */
9336                 SendToICS(ics_prefix);
9337                 SendToICS("draw\n");
9338             } else if (StrCaseStr(resultDetails, "resign")) {
9339                 SendToICS(ics_prefix);
9340                 SendToICS("resign\n");
9341             }
9342         }
9343 #endif
9344         endingGame = 0; /* [HGM] crash */
9345         return;
9346     }
9347
9348     /* If we're loading the game from a file, stop */
9349     if (whosays == GE_FILE) {
9350       (void) StopLoadGameTimer();
9351       gameFileFP = NULL;
9352     }
9353
9354     /* Cancel draw offers */
9355     first.offeredDraw = second.offeredDraw = 0;
9356
9357     /* If this is an ICS game, only ICS can really say it's done;
9358        if not, anyone can. */
9359     isIcsGame = (gameMode == IcsPlayingWhite ||
9360                  gameMode == IcsPlayingBlack ||
9361                  gameMode == IcsObserving    ||
9362                  gameMode == IcsExamining);
9363
9364     if (!isIcsGame || whosays == GE_ICS) {
9365         /* OK -- not an ICS game, or ICS said it was done */
9366         StopClocks();
9367         if (!isIcsGame && !appData.noChessProgram)
9368           SetUserThinkingEnables();
9369
9370         /* [HGM] if a machine claims the game end we verify this claim */
9371         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9372             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9373                 char claimer;
9374                 ChessMove trueResult = (ChessMove) -1;
9375
9376                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9377                                             first.twoMachinesColor[0] :
9378                                             second.twoMachinesColor[0] ;
9379
9380                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9381                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9382                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9383                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9384                 } else
9385                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9386                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9387                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9388                 } else
9389                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9390                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9391                 }
9392
9393                 // now verify win claims, but not in drop games, as we don't understand those yet
9394                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9395                                                  || gameInfo.variant == VariantGreat) &&
9396                     (result == WhiteWins && claimer == 'w' ||
9397                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9398                       if (appData.debugMode) {
9399                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9400                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9401                       }
9402                       if(result != trueResult) {
9403                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9404                               result = claimer == 'w' ? BlackWins : WhiteWins;
9405                               resultDetails = buf;
9406                       }
9407                 } else
9408                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9409                     && (forwardMostMove <= backwardMostMove ||
9410                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9411                         (claimer=='b')==(forwardMostMove&1))
9412                                                                                   ) {
9413                       /* [HGM] verify: draws that were not flagged are false claims */
9414                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9415                       result = claimer == 'w' ? BlackWins : WhiteWins;
9416                       resultDetails = buf;
9417                 }
9418                 /* (Claiming a loss is accepted no questions asked!) */
9419             }
9420             /* [HGM] bare: don't allow bare King to win */
9421             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9422                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9423                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9424                && result != GameIsDrawn)
9425             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9426                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9427                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9428                         if(p >= 0 && p <= (int)WhiteKing) k++;
9429                 }
9430                 if (appData.debugMode) {
9431                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9432                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9433                 }
9434                 if(k <= 1) {
9435                         result = GameIsDrawn;
9436                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9437                         resultDetails = buf;
9438                 }
9439             }
9440         }
9441
9442
9443         if(serverMoves != NULL && !loadFlag) { char c = '=';
9444             if(result==WhiteWins) c = '+';
9445             if(result==BlackWins) c = '-';
9446             if(resultDetails != NULL)
9447                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9448         }
9449         if (resultDetails != NULL) {
9450             gameInfo.result = result;
9451             gameInfo.resultDetails = StrSave(resultDetails);
9452
9453             /* display last move only if game was not loaded from file */
9454             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9455                 DisplayMove(currentMove - 1);
9456
9457             if (forwardMostMove != 0) {
9458                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9459                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9460                                                                 ) {
9461                     if (*appData.saveGameFile != NULLCHAR) {
9462                         SaveGameToFile(appData.saveGameFile, TRUE);
9463                     } else if (appData.autoSaveGames) {
9464                         AutoSaveGame();
9465                     }
9466                     if (*appData.savePositionFile != NULLCHAR) {
9467                         SavePositionToFile(appData.savePositionFile);
9468                     }
9469                 }
9470             }
9471
9472             /* Tell program how game ended in case it is learning */
9473             /* [HGM] Moved this to after saving the PGN, just in case */
9474             /* engine died and we got here through time loss. In that */
9475             /* case we will get a fatal error writing the pipe, which */
9476             /* would otherwise lose us the PGN.                       */
9477             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9478             /* output during GameEnds should never be fatal anymore   */
9479             if (gameMode == MachinePlaysWhite ||
9480                 gameMode == MachinePlaysBlack ||
9481                 gameMode == TwoMachinesPlay ||
9482                 gameMode == IcsPlayingWhite ||
9483                 gameMode == IcsPlayingBlack ||
9484                 gameMode == BeginningOfGame) {
9485                 char buf[MSG_SIZ];
9486                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9487                         resultDetails);
9488                 if (first.pr != NoProc) {
9489                     SendToProgram(buf, &first);
9490                 }
9491                 if (second.pr != NoProc &&
9492                     gameMode == TwoMachinesPlay) {
9493                     SendToProgram(buf, &second);
9494                 }
9495             }
9496         }
9497
9498         if (appData.icsActive) {
9499             if (appData.quietPlay &&
9500                 (gameMode == IcsPlayingWhite ||
9501                  gameMode == IcsPlayingBlack)) {
9502                 SendToICS(ics_prefix);
9503                 SendToICS("set shout 1\n");
9504             }
9505             nextGameMode = IcsIdle;
9506             ics_user_moved = FALSE;
9507             /* clean up premove.  It's ugly when the game has ended and the
9508              * premove highlights are still on the board.
9509              */
9510             if (gotPremove) {
9511               gotPremove = FALSE;
9512               ClearPremoveHighlights();
9513               DrawPosition(FALSE, boards[currentMove]);
9514             }
9515             if (whosays == GE_ICS) {
9516                 switch (result) {
9517                 case WhiteWins:
9518                     if (gameMode == IcsPlayingWhite)
9519                         PlayIcsWinSound();
9520                     else if(gameMode == IcsPlayingBlack)
9521                         PlayIcsLossSound();
9522                     break;
9523                 case BlackWins:
9524                     if (gameMode == IcsPlayingBlack)
9525                         PlayIcsWinSound();
9526                     else if(gameMode == IcsPlayingWhite)
9527                         PlayIcsLossSound();
9528                     break;
9529                 case GameIsDrawn:
9530                     PlayIcsDrawSound();
9531                     break;
9532                 default:
9533                     PlayIcsUnfinishedSound();
9534                 }
9535             }
9536         } else if (gameMode == EditGame ||
9537                    gameMode == PlayFromGameFile ||
9538                    gameMode == AnalyzeMode ||
9539                    gameMode == AnalyzeFile) {
9540             nextGameMode = gameMode;
9541         } else {
9542             nextGameMode = EndOfGame;
9543         }
9544         pausing = FALSE;
9545         ModeHighlight();
9546     } else {
9547         nextGameMode = gameMode;
9548     }
9549
9550     if (appData.noChessProgram) {
9551         gameMode = nextGameMode;
9552         ModeHighlight();
9553         endingGame = 0; /* [HGM] crash */
9554         return;
9555     }
9556
9557     if (first.reuse) {
9558         /* Put first chess program into idle state */
9559         if (first.pr != NoProc &&
9560             (gameMode == MachinePlaysWhite ||
9561              gameMode == MachinePlaysBlack ||
9562              gameMode == TwoMachinesPlay ||
9563              gameMode == IcsPlayingWhite ||
9564              gameMode == IcsPlayingBlack ||
9565              gameMode == BeginningOfGame)) {
9566             SendToProgram("force\n", &first);
9567             if (first.usePing) {
9568               char buf[MSG_SIZ];
9569               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9570               SendToProgram(buf, &first);
9571             }
9572         }
9573     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9574         /* Kill off first chess program */
9575         if (first.isr != NULL)
9576           RemoveInputSource(first.isr);
9577         first.isr = NULL;
9578
9579         if (first.pr != NoProc) {
9580             ExitAnalyzeMode();
9581             DoSleep( appData.delayBeforeQuit );
9582             SendToProgram("quit\n", &first);
9583             DoSleep( appData.delayAfterQuit );
9584             DestroyChildProcess(first.pr, first.useSigterm);
9585         }
9586         first.pr = NoProc;
9587     }
9588     if (second.reuse) {
9589         /* Put second chess program into idle state */
9590         if (second.pr != NoProc &&
9591             gameMode == TwoMachinesPlay) {
9592             SendToProgram("force\n", &second);
9593             if (second.usePing) {
9594               char buf[MSG_SIZ];
9595               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9596               SendToProgram(buf, &second);
9597             }
9598         }
9599     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9600         /* Kill off second chess program */
9601         if (second.isr != NULL)
9602           RemoveInputSource(second.isr);
9603         second.isr = NULL;
9604
9605         if (second.pr != NoProc) {
9606             DoSleep( appData.delayBeforeQuit );
9607             SendToProgram("quit\n", &second);
9608             DoSleep( appData.delayAfterQuit );
9609             DestroyChildProcess(second.pr, second.useSigterm);
9610         }
9611         second.pr = NoProc;
9612     }
9613
9614     if (matchMode && gameMode == TwoMachinesPlay) {
9615         switch (result) {
9616         case WhiteWins:
9617           if (first.twoMachinesColor[0] == 'w') {
9618             first.matchWins++;
9619           } else {
9620             second.matchWins++;
9621           }
9622           break;
9623         case BlackWins:
9624           if (first.twoMachinesColor[0] == 'b') {
9625             first.matchWins++;
9626           } else {
9627             second.matchWins++;
9628           }
9629           break;
9630         default:
9631           break;
9632         }
9633         if (matchGame < appData.matchGames) {
9634             char *tmp;
9635             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9636                 tmp = first.twoMachinesColor;
9637                 first.twoMachinesColor = second.twoMachinesColor;
9638                 second.twoMachinesColor = tmp;
9639             }
9640             gameMode = nextGameMode;
9641             matchGame++;
9642             if(appData.matchPause>10000 || appData.matchPause<10)
9643                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9644             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9645             endingGame = 0; /* [HGM] crash */
9646             return;
9647         } else {
9648             gameMode = nextGameMode;
9649             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9650                      first.tidy, second.tidy,
9651                      first.matchWins, second.matchWins,
9652                      appData.matchGames - (first.matchWins + second.matchWins));
9653             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9654             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9655                 first.twoMachinesColor = "black\n";
9656                 second.twoMachinesColor = "white\n";
9657             } else {
9658                 first.twoMachinesColor = "white\n";
9659                 second.twoMachinesColor = "black\n";
9660             }
9661         }
9662     }
9663     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9664         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9665       ExitAnalyzeMode();
9666     gameMode = nextGameMode;
9667     ModeHighlight();
9668     endingGame = 0;  /* [HGM] crash */
9669     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9670       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9671         matchMode = FALSE; appData.matchGames = matchGame = 0;
9672         DisplayNote(buf);
9673       }
9674     }
9675 }
9676
9677 /* Assumes program was just initialized (initString sent).
9678    Leaves program in force mode. */
9679 void
9680 FeedMovesToProgram(cps, upto)
9681      ChessProgramState *cps;
9682      int upto;
9683 {
9684     int i;
9685
9686     if (appData.debugMode)
9687       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9688               startedFromSetupPosition ? "position and " : "",
9689               backwardMostMove, upto, cps->which);
9690     if(currentlyInitializedVariant != gameInfo.variant) {
9691       char buf[MSG_SIZ];
9692         // [HGM] variantswitch: make engine aware of new variant
9693         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9694                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9695         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9696         SendToProgram(buf, cps);
9697         currentlyInitializedVariant = gameInfo.variant;
9698     }
9699     SendToProgram("force\n", cps);
9700     if (startedFromSetupPosition) {
9701         SendBoard(cps, backwardMostMove);
9702     if (appData.debugMode) {
9703         fprintf(debugFP, "feedMoves\n");
9704     }
9705     }
9706     for (i = backwardMostMove; i < upto; i++) {
9707         SendMoveToProgram(i, cps);
9708     }
9709 }
9710
9711
9712 void
9713 ResurrectChessProgram()
9714 {
9715      /* The chess program may have exited.
9716         If so, restart it and feed it all the moves made so far. */
9717
9718     if (appData.noChessProgram || first.pr != NoProc) return;
9719
9720     StartChessProgram(&first);
9721     InitChessProgram(&first, FALSE);
9722     FeedMovesToProgram(&first, currentMove);
9723
9724     if (!first.sendTime) {
9725         /* can't tell gnuchess what its clock should read,
9726            so we bow to its notion. */
9727         ResetClocks();
9728         timeRemaining[0][currentMove] = whiteTimeRemaining;
9729         timeRemaining[1][currentMove] = blackTimeRemaining;
9730     }
9731
9732     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9733                 appData.icsEngineAnalyze) && first.analysisSupport) {
9734       SendToProgram("analyze\n", &first);
9735       first.analyzing = TRUE;
9736     }
9737 }
9738
9739 /*
9740  * Button procedures
9741  */
9742 void
9743 Reset(redraw, init)
9744      int redraw, init;
9745 {
9746     int i;
9747
9748     if (appData.debugMode) {
9749         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9750                 redraw, init, gameMode);
9751     }
9752     CleanupTail(); // [HGM] vari: delete any stored variations
9753     pausing = pauseExamInvalid = FALSE;
9754     startedFromSetupPosition = blackPlaysFirst = FALSE;
9755     firstMove = TRUE;
9756     whiteFlag = blackFlag = FALSE;
9757     userOfferedDraw = FALSE;
9758     hintRequested = bookRequested = FALSE;
9759     first.maybeThinking = FALSE;
9760     second.maybeThinking = FALSE;
9761     first.bookSuspend = FALSE; // [HGM] book
9762     second.bookSuspend = FALSE;
9763     thinkOutput[0] = NULLCHAR;
9764     lastHint[0] = NULLCHAR;
9765     ClearGameInfo(&gameInfo);
9766     gameInfo.variant = StringToVariant(appData.variant);
9767     ics_user_moved = ics_clock_paused = FALSE;
9768     ics_getting_history = H_FALSE;
9769     ics_gamenum = -1;
9770     white_holding[0] = black_holding[0] = NULLCHAR;
9771     ClearProgramStats();
9772     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9773
9774     ResetFrontEnd();
9775     ClearHighlights();
9776     flipView = appData.flipView;
9777     ClearPremoveHighlights();
9778     gotPremove = FALSE;
9779     alarmSounded = FALSE;
9780
9781     GameEnds(EndOfFile, NULL, GE_PLAYER);
9782     if(appData.serverMovesName != NULL) {
9783         /* [HGM] prepare to make moves file for broadcasting */
9784         clock_t t = clock();
9785         if(serverMoves != NULL) fclose(serverMoves);
9786         serverMoves = fopen(appData.serverMovesName, "r");
9787         if(serverMoves != NULL) {
9788             fclose(serverMoves);
9789             /* delay 15 sec before overwriting, so all clients can see end */
9790             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9791         }
9792         serverMoves = fopen(appData.serverMovesName, "w");
9793     }
9794
9795     ExitAnalyzeMode();
9796     gameMode = BeginningOfGame;
9797     ModeHighlight();
9798     if(appData.icsActive) gameInfo.variant = VariantNormal;
9799     currentMove = forwardMostMove = backwardMostMove = 0;
9800     InitPosition(redraw);
9801     for (i = 0; i < MAX_MOVES; i++) {
9802         if (commentList[i] != NULL) {
9803             free(commentList[i]);
9804             commentList[i] = NULL;
9805         }
9806     }
9807     ResetClocks();
9808     timeRemaining[0][0] = whiteTimeRemaining;
9809     timeRemaining[1][0] = blackTimeRemaining;
9810     if (first.pr == NULL) {
9811         StartChessProgram(&first);
9812     }
9813     if (init) {
9814             InitChessProgram(&first, startedFromSetupPosition);
9815     }
9816     DisplayTitle("");
9817     DisplayMessage("", "");
9818     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9819     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9820 }
9821
9822 void
9823 AutoPlayGameLoop()
9824 {
9825     for (;;) {
9826         if (!AutoPlayOneMove())
9827           return;
9828         if (matchMode || appData.timeDelay == 0)
9829           continue;
9830         if (appData.timeDelay < 0)
9831           return;
9832         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9833         break;
9834     }
9835 }
9836
9837
9838 int
9839 AutoPlayOneMove()
9840 {
9841     int fromX, fromY, toX, toY;
9842
9843     if (appData.debugMode) {
9844       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9845     }
9846
9847     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9848       return FALSE;
9849
9850     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9851       pvInfoList[currentMove].depth = programStats.depth;
9852       pvInfoList[currentMove].score = programStats.score;
9853       pvInfoList[currentMove].time  = 0;
9854       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9855     }
9856
9857     if (currentMove >= forwardMostMove) {
9858       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9859       gameMode = EditGame;
9860       ModeHighlight();
9861
9862       /* [AS] Clear current move marker at the end of a game */
9863       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9864
9865       return FALSE;
9866     }
9867
9868     toX = moveList[currentMove][2] - AAA;
9869     toY = moveList[currentMove][3] - ONE;
9870
9871     if (moveList[currentMove][1] == '@') {
9872         if (appData.highlightLastMove) {
9873             SetHighlights(-1, -1, toX, toY);
9874         }
9875     } else {
9876         fromX = moveList[currentMove][0] - AAA;
9877         fromY = moveList[currentMove][1] - ONE;
9878
9879         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9880
9881         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9882
9883         if (appData.highlightLastMove) {
9884             SetHighlights(fromX, fromY, toX, toY);
9885         }
9886     }
9887     DisplayMove(currentMove);
9888     SendMoveToProgram(currentMove++, &first);
9889     DisplayBothClocks();
9890     DrawPosition(FALSE, boards[currentMove]);
9891     // [HGM] PV info: always display, routine tests if empty
9892     DisplayComment(currentMove - 1, commentList[currentMove]);
9893     return TRUE;
9894 }
9895
9896
9897 int
9898 LoadGameOneMove(readAhead)
9899      ChessMove readAhead;
9900 {
9901     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9902     char promoChar = NULLCHAR;
9903     ChessMove moveType;
9904     char move[MSG_SIZ];
9905     char *p, *q;
9906
9907     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9908         gameMode != AnalyzeMode && gameMode != Training) {
9909         gameFileFP = NULL;
9910         return FALSE;
9911     }
9912
9913     yyboardindex = forwardMostMove;
9914     if (readAhead != EndOfFile) {
9915       moveType = readAhead;
9916     } else {
9917       if (gameFileFP == NULL)
9918           return FALSE;
9919       moveType = (ChessMove) Myylex();
9920     }
9921
9922     done = FALSE;
9923     switch (moveType) {
9924       case Comment:
9925         if (appData.debugMode)
9926           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9927         p = yy_text;
9928
9929         /* append the comment but don't display it */
9930         AppendComment(currentMove, p, FALSE);
9931         return TRUE;
9932
9933       case WhiteCapturesEnPassant:
9934       case BlackCapturesEnPassant:
9935       case WhitePromotion:
9936       case BlackPromotion:
9937       case WhiteNonPromotion:
9938       case BlackNonPromotion:
9939       case NormalMove:
9940       case WhiteKingSideCastle:
9941       case WhiteQueenSideCastle:
9942       case BlackKingSideCastle:
9943       case BlackQueenSideCastle:
9944       case WhiteKingSideCastleWild:
9945       case WhiteQueenSideCastleWild:
9946       case BlackKingSideCastleWild:
9947       case BlackQueenSideCastleWild:
9948       /* PUSH Fabien */
9949       case WhiteHSideCastleFR:
9950       case WhiteASideCastleFR:
9951       case BlackHSideCastleFR:
9952       case BlackASideCastleFR:
9953       /* POP Fabien */
9954         if (appData.debugMode)
9955           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9956         fromX = currentMoveString[0] - AAA;
9957         fromY = currentMoveString[1] - ONE;
9958         toX = currentMoveString[2] - AAA;
9959         toY = currentMoveString[3] - ONE;
9960         promoChar = currentMoveString[4];
9961         break;
9962
9963       case WhiteDrop:
9964       case BlackDrop:
9965         if (appData.debugMode)
9966           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9967         fromX = moveType == WhiteDrop ?
9968           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9969         (int) CharToPiece(ToLower(currentMoveString[0]));
9970         fromY = DROP_RANK;
9971         toX = currentMoveString[2] - AAA;
9972         toY = currentMoveString[3] - ONE;
9973         break;
9974
9975       case WhiteWins:
9976       case BlackWins:
9977       case GameIsDrawn:
9978       case GameUnfinished:
9979         if (appData.debugMode)
9980           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9981         p = strchr(yy_text, '{');
9982         if (p == NULL) p = strchr(yy_text, '(');
9983         if (p == NULL) {
9984             p = yy_text;
9985             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9986         } else {
9987             q = strchr(p, *p == '{' ? '}' : ')');
9988             if (q != NULL) *q = NULLCHAR;
9989             p++;
9990         }
9991         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9992         GameEnds(moveType, p, GE_FILE);
9993         done = TRUE;
9994         if (cmailMsgLoaded) {
9995             ClearHighlights();
9996             flipView = WhiteOnMove(currentMove);
9997             if (moveType == GameUnfinished) flipView = !flipView;
9998             if (appData.debugMode)
9999               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10000         }
10001         break;
10002
10003       case EndOfFile:
10004         if (appData.debugMode)
10005           fprintf(debugFP, "Parser hit end of file\n");
10006         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10007           case MT_NONE:
10008           case MT_CHECK:
10009             break;
10010           case MT_CHECKMATE:
10011           case MT_STAINMATE:
10012             if (WhiteOnMove(currentMove)) {
10013                 GameEnds(BlackWins, "Black mates", GE_FILE);
10014             } else {
10015                 GameEnds(WhiteWins, "White mates", GE_FILE);
10016             }
10017             break;
10018           case MT_STALEMATE:
10019             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10020             break;
10021         }
10022         done = TRUE;
10023         break;
10024
10025       case MoveNumberOne:
10026         if (lastLoadGameStart == GNUChessGame) {
10027             /* GNUChessGames have numbers, but they aren't move numbers */
10028             if (appData.debugMode)
10029               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10030                       yy_text, (int) moveType);
10031             return LoadGameOneMove(EndOfFile); /* tail recursion */
10032         }
10033         /* else fall thru */
10034
10035       case XBoardGame:
10036       case GNUChessGame:
10037       case PGNTag:
10038         /* Reached start of next game in file */
10039         if (appData.debugMode)
10040           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10041         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10042           case MT_NONE:
10043           case MT_CHECK:
10044             break;
10045           case MT_CHECKMATE:
10046           case MT_STAINMATE:
10047             if (WhiteOnMove(currentMove)) {
10048                 GameEnds(BlackWins, "Black mates", GE_FILE);
10049             } else {
10050                 GameEnds(WhiteWins, "White mates", GE_FILE);
10051             }
10052             break;
10053           case MT_STALEMATE:
10054             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10055             break;
10056         }
10057         done = TRUE;
10058         break;
10059
10060       case PositionDiagram:     /* should not happen; ignore */
10061       case ElapsedTime:         /* ignore */
10062       case NAG:                 /* ignore */
10063         if (appData.debugMode)
10064           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10065                   yy_text, (int) moveType);
10066         return LoadGameOneMove(EndOfFile); /* tail recursion */
10067
10068       case IllegalMove:
10069         if (appData.testLegality) {
10070             if (appData.debugMode)
10071               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10072             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10073                     (forwardMostMove / 2) + 1,
10074                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10075             DisplayError(move, 0);
10076             done = TRUE;
10077         } else {
10078             if (appData.debugMode)
10079               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10080                       yy_text, currentMoveString);
10081             fromX = currentMoveString[0] - AAA;
10082             fromY = currentMoveString[1] - ONE;
10083             toX = currentMoveString[2] - AAA;
10084             toY = currentMoveString[3] - ONE;
10085             promoChar = currentMoveString[4];
10086         }
10087         break;
10088
10089       case AmbiguousMove:
10090         if (appData.debugMode)
10091           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10092         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10093                 (forwardMostMove / 2) + 1,
10094                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10095         DisplayError(move, 0);
10096         done = TRUE;
10097         break;
10098
10099       default:
10100       case ImpossibleMove:
10101         if (appData.debugMode)
10102           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10103         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10104                 (forwardMostMove / 2) + 1,
10105                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10106         DisplayError(move, 0);
10107         done = TRUE;
10108         break;
10109     }
10110
10111     if (done) {
10112         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10113             DrawPosition(FALSE, boards[currentMove]);
10114             DisplayBothClocks();
10115             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10116               DisplayComment(currentMove - 1, commentList[currentMove]);
10117         }
10118         (void) StopLoadGameTimer();
10119         gameFileFP = NULL;
10120         cmailOldMove = forwardMostMove;
10121         return FALSE;
10122     } else {
10123         /* currentMoveString is set as a side-effect of yylex */
10124
10125         thinkOutput[0] = NULLCHAR;
10126         MakeMove(fromX, fromY, toX, toY, promoChar);
10127         currentMove = forwardMostMove;
10128         return TRUE;
10129     }
10130 }
10131
10132 /* Load the nth game from the given file */
10133 int
10134 LoadGameFromFile(filename, n, title, useList)
10135      char *filename;
10136      int n;
10137      char *title;
10138      /*Boolean*/ int useList;
10139 {
10140     FILE *f;
10141     char buf[MSG_SIZ];
10142
10143     if (strcmp(filename, "-") == 0) {
10144         f = stdin;
10145         title = "stdin";
10146     } else {
10147         f = fopen(filename, "rb");
10148         if (f == NULL) {
10149           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10150             DisplayError(buf, errno);
10151             return FALSE;
10152         }
10153     }
10154     if (fseek(f, 0, 0) == -1) {
10155         /* f is not seekable; probably a pipe */
10156         useList = FALSE;
10157     }
10158     if (useList && n == 0) {
10159         int error = GameListBuild(f);
10160         if (error) {
10161             DisplayError(_("Cannot build game list"), error);
10162         } else if (!ListEmpty(&gameList) &&
10163                    ((ListGame *) gameList.tailPred)->number > 1) {
10164             GameListPopUp(f, title);
10165             return TRUE;
10166         }
10167         GameListDestroy();
10168         n = 1;
10169     }
10170     if (n == 0) n = 1;
10171     return LoadGame(f, n, title, FALSE);
10172 }
10173
10174
10175 void
10176 MakeRegisteredMove()
10177 {
10178     int fromX, fromY, toX, toY;
10179     char promoChar;
10180     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10181         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10182           case CMAIL_MOVE:
10183           case CMAIL_DRAW:
10184             if (appData.debugMode)
10185               fprintf(debugFP, "Restoring %s for game %d\n",
10186                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10187
10188             thinkOutput[0] = NULLCHAR;
10189             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10190             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10191             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10192             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10193             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10194             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10195             MakeMove(fromX, fromY, toX, toY, promoChar);
10196             ShowMove(fromX, fromY, toX, toY);
10197
10198             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10199               case MT_NONE:
10200               case MT_CHECK:
10201                 break;
10202
10203               case MT_CHECKMATE:
10204               case MT_STAINMATE:
10205                 if (WhiteOnMove(currentMove)) {
10206                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10207                 } else {
10208                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10209                 }
10210                 break;
10211
10212               case MT_STALEMATE:
10213                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10214                 break;
10215             }
10216
10217             break;
10218
10219           case CMAIL_RESIGN:
10220             if (WhiteOnMove(currentMove)) {
10221                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10222             } else {
10223                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10224             }
10225             break;
10226
10227           case CMAIL_ACCEPT:
10228             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10229             break;
10230
10231           default:
10232             break;
10233         }
10234     }
10235
10236     return;
10237 }
10238
10239 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10240 int
10241 CmailLoadGame(f, gameNumber, title, useList)
10242      FILE *f;
10243      int gameNumber;
10244      char *title;
10245      int useList;
10246 {
10247     int retVal;
10248
10249     if (gameNumber > nCmailGames) {
10250         DisplayError(_("No more games in this message"), 0);
10251         return FALSE;
10252     }
10253     if (f == lastLoadGameFP) {
10254         int offset = gameNumber - lastLoadGameNumber;
10255         if (offset == 0) {
10256             cmailMsg[0] = NULLCHAR;
10257             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10258                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10259                 nCmailMovesRegistered--;
10260             }
10261             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10262             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10263                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10264             }
10265         } else {
10266             if (! RegisterMove()) return FALSE;
10267         }
10268     }
10269
10270     retVal = LoadGame(f, gameNumber, title, useList);
10271
10272     /* Make move registered during previous look at this game, if any */
10273     MakeRegisteredMove();
10274
10275     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10276         commentList[currentMove]
10277           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10278         DisplayComment(currentMove - 1, commentList[currentMove]);
10279     }
10280
10281     return retVal;
10282 }
10283
10284 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10285 int
10286 ReloadGame(offset)
10287      int offset;
10288 {
10289     int gameNumber = lastLoadGameNumber + offset;
10290     if (lastLoadGameFP == NULL) {
10291         DisplayError(_("No game has been loaded yet"), 0);
10292         return FALSE;
10293     }
10294     if (gameNumber <= 0) {
10295         DisplayError(_("Can't back up any further"), 0);
10296         return FALSE;
10297     }
10298     if (cmailMsgLoaded) {
10299         return CmailLoadGame(lastLoadGameFP, gameNumber,
10300                              lastLoadGameTitle, lastLoadGameUseList);
10301     } else {
10302         return LoadGame(lastLoadGameFP, gameNumber,
10303                         lastLoadGameTitle, lastLoadGameUseList);
10304     }
10305 }
10306
10307
10308
10309 /* Load the nth game from open file f */
10310 int
10311 LoadGame(f, gameNumber, title, useList)
10312      FILE *f;
10313      int gameNumber;
10314      char *title;
10315      int useList;
10316 {
10317     ChessMove cm;
10318     char buf[MSG_SIZ];
10319     int gn = gameNumber;
10320     ListGame *lg = NULL;
10321     int numPGNTags = 0;
10322     int err;
10323     GameMode oldGameMode;
10324     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10325
10326     if (appData.debugMode)
10327         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10328
10329     if (gameMode == Training )
10330         SetTrainingModeOff();
10331
10332     oldGameMode = gameMode;
10333     if (gameMode != BeginningOfGame) {
10334       Reset(FALSE, TRUE);
10335     }
10336
10337     gameFileFP = f;
10338     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10339         fclose(lastLoadGameFP);
10340     }
10341
10342     if (useList) {
10343         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10344
10345         if (lg) {
10346             fseek(f, lg->offset, 0);
10347             GameListHighlight(gameNumber);
10348             gn = 1;
10349         }
10350         else {
10351             DisplayError(_("Game number out of range"), 0);
10352             return FALSE;
10353         }
10354     } else {
10355         GameListDestroy();
10356         if (fseek(f, 0, 0) == -1) {
10357             if (f == lastLoadGameFP ?
10358                 gameNumber == lastLoadGameNumber + 1 :
10359                 gameNumber == 1) {
10360                 gn = 1;
10361             } else {
10362                 DisplayError(_("Can't seek on game file"), 0);
10363                 return FALSE;
10364             }
10365         }
10366     }
10367     lastLoadGameFP = f;
10368     lastLoadGameNumber = gameNumber;
10369     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10370     lastLoadGameUseList = useList;
10371
10372     yynewfile(f);
10373
10374     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10375       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10376                 lg->gameInfo.black);
10377             DisplayTitle(buf);
10378     } else if (*title != NULLCHAR) {
10379         if (gameNumber > 1) {
10380           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10381             DisplayTitle(buf);
10382         } else {
10383             DisplayTitle(title);
10384         }
10385     }
10386
10387     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10388         gameMode = PlayFromGameFile;
10389         ModeHighlight();
10390     }
10391
10392     currentMove = forwardMostMove = backwardMostMove = 0;
10393     CopyBoard(boards[0], initialPosition);
10394     StopClocks();
10395
10396     /*
10397      * Skip the first gn-1 games in the file.
10398      * Also skip over anything that precedes an identifiable
10399      * start of game marker, to avoid being confused by
10400      * garbage at the start of the file.  Currently
10401      * recognized start of game markers are the move number "1",
10402      * the pattern "gnuchess .* game", the pattern
10403      * "^[#;%] [^ ]* game file", and a PGN tag block.
10404      * A game that starts with one of the latter two patterns
10405      * will also have a move number 1, possibly
10406      * following a position diagram.
10407      * 5-4-02: Let's try being more lenient and allowing a game to
10408      * start with an unnumbered move.  Does that break anything?
10409      */
10410     cm = lastLoadGameStart = EndOfFile;
10411     while (gn > 0) {
10412         yyboardindex = forwardMostMove;
10413         cm = (ChessMove) Myylex();
10414         switch (cm) {
10415           case EndOfFile:
10416             if (cmailMsgLoaded) {
10417                 nCmailGames = CMAIL_MAX_GAMES - gn;
10418             } else {
10419                 Reset(TRUE, TRUE);
10420                 DisplayError(_("Game not found in file"), 0);
10421             }
10422             return FALSE;
10423
10424           case GNUChessGame:
10425           case XBoardGame:
10426             gn--;
10427             lastLoadGameStart = cm;
10428             break;
10429
10430           case MoveNumberOne:
10431             switch (lastLoadGameStart) {
10432               case GNUChessGame:
10433               case XBoardGame:
10434               case PGNTag:
10435                 break;
10436               case MoveNumberOne:
10437               case EndOfFile:
10438                 gn--;           /* count this game */
10439                 lastLoadGameStart = cm;
10440                 break;
10441               default:
10442                 /* impossible */
10443                 break;
10444             }
10445             break;
10446
10447           case PGNTag:
10448             switch (lastLoadGameStart) {
10449               case GNUChessGame:
10450               case PGNTag:
10451               case MoveNumberOne:
10452               case EndOfFile:
10453                 gn--;           /* count this game */
10454                 lastLoadGameStart = cm;
10455                 break;
10456               case XBoardGame:
10457                 lastLoadGameStart = cm; /* game counted already */
10458                 break;
10459               default:
10460                 /* impossible */
10461                 break;
10462             }
10463             if (gn > 0) {
10464                 do {
10465                     yyboardindex = forwardMostMove;
10466                     cm = (ChessMove) Myylex();
10467                 } while (cm == PGNTag || cm == Comment);
10468             }
10469             break;
10470
10471           case WhiteWins:
10472           case BlackWins:
10473           case GameIsDrawn:
10474             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10475                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10476                     != CMAIL_OLD_RESULT) {
10477                     nCmailResults ++ ;
10478                     cmailResult[  CMAIL_MAX_GAMES
10479                                 - gn - 1] = CMAIL_OLD_RESULT;
10480                 }
10481             }
10482             break;
10483
10484           case NormalMove:
10485             /* Only a NormalMove can be at the start of a game
10486              * without a position diagram. */
10487             if (lastLoadGameStart == EndOfFile ) {
10488               gn--;
10489               lastLoadGameStart = MoveNumberOne;
10490             }
10491             break;
10492
10493           default:
10494             break;
10495         }
10496     }
10497
10498     if (appData.debugMode)
10499       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10500
10501     if (cm == XBoardGame) {
10502         /* Skip any header junk before position diagram and/or move 1 */
10503         for (;;) {
10504             yyboardindex = forwardMostMove;
10505             cm = (ChessMove) Myylex();
10506
10507             if (cm == EndOfFile ||
10508                 cm == GNUChessGame || cm == XBoardGame) {
10509                 /* Empty game; pretend end-of-file and handle later */
10510                 cm = EndOfFile;
10511                 break;
10512             }
10513
10514             if (cm == MoveNumberOne || cm == PositionDiagram ||
10515                 cm == PGNTag || cm == Comment)
10516               break;
10517         }
10518     } else if (cm == GNUChessGame) {
10519         if (gameInfo.event != NULL) {
10520             free(gameInfo.event);
10521         }
10522         gameInfo.event = StrSave(yy_text);
10523     }
10524
10525     startedFromSetupPosition = FALSE;
10526     while (cm == PGNTag) {
10527         if (appData.debugMode)
10528           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10529         err = ParsePGNTag(yy_text, &gameInfo);
10530         if (!err) numPGNTags++;
10531
10532         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10533         if(gameInfo.variant != oldVariant) {
10534             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10535             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10536             InitPosition(TRUE);
10537             oldVariant = gameInfo.variant;
10538             if (appData.debugMode)
10539               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10540         }
10541
10542
10543         if (gameInfo.fen != NULL) {
10544           Board initial_position;
10545           startedFromSetupPosition = TRUE;
10546           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10547             Reset(TRUE, TRUE);
10548             DisplayError(_("Bad FEN position in file"), 0);
10549             return FALSE;
10550           }
10551           CopyBoard(boards[0], initial_position);
10552           if (blackPlaysFirst) {
10553             currentMove = forwardMostMove = backwardMostMove = 1;
10554             CopyBoard(boards[1], initial_position);
10555             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10556             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10557             timeRemaining[0][1] = whiteTimeRemaining;
10558             timeRemaining[1][1] = blackTimeRemaining;
10559             if (commentList[0] != NULL) {
10560               commentList[1] = commentList[0];
10561               commentList[0] = NULL;
10562             }
10563           } else {
10564             currentMove = forwardMostMove = backwardMostMove = 0;
10565           }
10566           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10567           {   int i;
10568               initialRulePlies = FENrulePlies;
10569               for( i=0; i< nrCastlingRights; i++ )
10570                   initialRights[i] = initial_position[CASTLING][i];
10571           }
10572           yyboardindex = forwardMostMove;
10573           free(gameInfo.fen);
10574           gameInfo.fen = NULL;
10575         }
10576
10577         yyboardindex = forwardMostMove;
10578         cm = (ChessMove) Myylex();
10579
10580         /* Handle comments interspersed among the tags */
10581         while (cm == Comment) {
10582             char *p;
10583             if (appData.debugMode)
10584               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10585             p = yy_text;
10586             AppendComment(currentMove, p, FALSE);
10587             yyboardindex = forwardMostMove;
10588             cm = (ChessMove) Myylex();
10589         }
10590     }
10591
10592     /* don't rely on existence of Event tag since if game was
10593      * pasted from clipboard the Event tag may not exist
10594      */
10595     if (numPGNTags > 0){
10596         char *tags;
10597         if (gameInfo.variant == VariantNormal) {
10598           VariantClass v = StringToVariant(gameInfo.event);
10599           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10600           if(v < VariantShogi) gameInfo.variant = v;
10601         }
10602         if (!matchMode) {
10603           if( appData.autoDisplayTags ) {
10604             tags = PGNTags(&gameInfo);
10605             TagsPopUp(tags, CmailMsg());
10606             free(tags);
10607           }
10608         }
10609     } else {
10610         /* Make something up, but don't display it now */
10611         SetGameInfo();
10612         TagsPopDown();
10613     }
10614
10615     if (cm == PositionDiagram) {
10616         int i, j;
10617         char *p;
10618         Board initial_position;
10619
10620         if (appData.debugMode)
10621           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10622
10623         if (!startedFromSetupPosition) {
10624             p = yy_text;
10625             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10626               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10627                 switch (*p) {
10628                   case '{':
10629                   case '[':
10630                   case '-':
10631                   case ' ':
10632                   case '\t':
10633                   case '\n':
10634                   case '\r':
10635                     break;
10636                   default:
10637                     initial_position[i][j++] = CharToPiece(*p);
10638                     break;
10639                 }
10640             while (*p == ' ' || *p == '\t' ||
10641                    *p == '\n' || *p == '\r') p++;
10642
10643             if (strncmp(p, "black", strlen("black"))==0)
10644               blackPlaysFirst = TRUE;
10645             else
10646               blackPlaysFirst = FALSE;
10647             startedFromSetupPosition = TRUE;
10648
10649             CopyBoard(boards[0], initial_position);
10650             if (blackPlaysFirst) {
10651                 currentMove = forwardMostMove = backwardMostMove = 1;
10652                 CopyBoard(boards[1], initial_position);
10653                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10654                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10655                 timeRemaining[0][1] = whiteTimeRemaining;
10656                 timeRemaining[1][1] = blackTimeRemaining;
10657                 if (commentList[0] != NULL) {
10658                     commentList[1] = commentList[0];
10659                     commentList[0] = NULL;
10660                 }
10661             } else {
10662                 currentMove = forwardMostMove = backwardMostMove = 0;
10663             }
10664         }
10665         yyboardindex = forwardMostMove;
10666         cm = (ChessMove) Myylex();
10667     }
10668
10669     if (first.pr == NoProc) {
10670         StartChessProgram(&first);
10671     }
10672     InitChessProgram(&first, FALSE);
10673     SendToProgram("force\n", &first);
10674     if (startedFromSetupPosition) {
10675         SendBoard(&first, forwardMostMove);
10676     if (appData.debugMode) {
10677         fprintf(debugFP, "Load Game\n");
10678     }
10679         DisplayBothClocks();
10680     }
10681
10682     /* [HGM] server: flag to write setup moves in broadcast file as one */
10683     loadFlag = appData.suppressLoadMoves;
10684
10685     while (cm == Comment) {
10686         char *p;
10687         if (appData.debugMode)
10688           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10689         p = yy_text;
10690         AppendComment(currentMove, p, FALSE);
10691         yyboardindex = forwardMostMove;
10692         cm = (ChessMove) Myylex();
10693     }
10694
10695     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10696         cm == WhiteWins || cm == BlackWins ||
10697         cm == GameIsDrawn || cm == GameUnfinished) {
10698         DisplayMessage("", _("No moves in game"));
10699         if (cmailMsgLoaded) {
10700             if (appData.debugMode)
10701               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10702             ClearHighlights();
10703             flipView = FALSE;
10704         }
10705         DrawPosition(FALSE, boards[currentMove]);
10706         DisplayBothClocks();
10707         gameMode = EditGame;
10708         ModeHighlight();
10709         gameFileFP = NULL;
10710         cmailOldMove = 0;
10711         return TRUE;
10712     }
10713
10714     // [HGM] PV info: routine tests if comment empty
10715     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10716         DisplayComment(currentMove - 1, commentList[currentMove]);
10717     }
10718     if (!matchMode && appData.timeDelay != 0)
10719       DrawPosition(FALSE, boards[currentMove]);
10720
10721     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10722       programStats.ok_to_send = 1;
10723     }
10724
10725     /* if the first token after the PGN tags is a move
10726      * and not move number 1, retrieve it from the parser
10727      */
10728     if (cm != MoveNumberOne)
10729         LoadGameOneMove(cm);
10730
10731     /* load the remaining moves from the file */
10732     while (LoadGameOneMove(EndOfFile)) {
10733       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10734       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10735     }
10736
10737     /* rewind to the start of the game */
10738     currentMove = backwardMostMove;
10739
10740     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10741
10742     if (oldGameMode == AnalyzeFile ||
10743         oldGameMode == AnalyzeMode) {
10744       AnalyzeFileEvent();
10745     }
10746
10747     if (matchMode || appData.timeDelay == 0) {
10748       ToEndEvent();
10749       gameMode = EditGame;
10750       ModeHighlight();
10751     } else if (appData.timeDelay > 0) {
10752       AutoPlayGameLoop();
10753     }
10754
10755     if (appData.debugMode)
10756         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10757
10758     loadFlag = 0; /* [HGM] true game starts */
10759     return TRUE;
10760 }
10761
10762 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10763 int
10764 ReloadPosition(offset)
10765      int offset;
10766 {
10767     int positionNumber = lastLoadPositionNumber + offset;
10768     if (lastLoadPositionFP == NULL) {
10769         DisplayError(_("No position has been loaded yet"), 0);
10770         return FALSE;
10771     }
10772     if (positionNumber <= 0) {
10773         DisplayError(_("Can't back up any further"), 0);
10774         return FALSE;
10775     }
10776     return LoadPosition(lastLoadPositionFP, positionNumber,
10777                         lastLoadPositionTitle);
10778 }
10779
10780 /* Load the nth position from the given file */
10781 int
10782 LoadPositionFromFile(filename, n, title)
10783      char *filename;
10784      int n;
10785      char *title;
10786 {
10787     FILE *f;
10788     char buf[MSG_SIZ];
10789
10790     if (strcmp(filename, "-") == 0) {
10791         return LoadPosition(stdin, n, "stdin");
10792     } else {
10793         f = fopen(filename, "rb");
10794         if (f == NULL) {
10795             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10796             DisplayError(buf, errno);
10797             return FALSE;
10798         } else {
10799             return LoadPosition(f, n, title);
10800         }
10801     }
10802 }
10803
10804 /* Load the nth position from the given open file, and close it */
10805 int
10806 LoadPosition(f, positionNumber, title)
10807      FILE *f;
10808      int positionNumber;
10809      char *title;
10810 {
10811     char *p, line[MSG_SIZ];
10812     Board initial_position;
10813     int i, j, fenMode, pn;
10814
10815     if (gameMode == Training )
10816         SetTrainingModeOff();
10817
10818     if (gameMode != BeginningOfGame) {
10819         Reset(FALSE, TRUE);
10820     }
10821     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10822         fclose(lastLoadPositionFP);
10823     }
10824     if (positionNumber == 0) positionNumber = 1;
10825     lastLoadPositionFP = f;
10826     lastLoadPositionNumber = positionNumber;
10827     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10828     if (first.pr == NoProc) {
10829       StartChessProgram(&first);
10830       InitChessProgram(&first, FALSE);
10831     }
10832     pn = positionNumber;
10833     if (positionNumber < 0) {
10834         /* Negative position number means to seek to that byte offset */
10835         if (fseek(f, -positionNumber, 0) == -1) {
10836             DisplayError(_("Can't seek on position file"), 0);
10837             return FALSE;
10838         };
10839         pn = 1;
10840     } else {
10841         if (fseek(f, 0, 0) == -1) {
10842             if (f == lastLoadPositionFP ?
10843                 positionNumber == lastLoadPositionNumber + 1 :
10844                 positionNumber == 1) {
10845                 pn = 1;
10846             } else {
10847                 DisplayError(_("Can't seek on position file"), 0);
10848                 return FALSE;
10849             }
10850         }
10851     }
10852     /* See if this file is FEN or old-style xboard */
10853     if (fgets(line, MSG_SIZ, f) == NULL) {
10854         DisplayError(_("Position not found in file"), 0);
10855         return FALSE;
10856     }
10857     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10858     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10859
10860     if (pn >= 2) {
10861         if (fenMode || line[0] == '#') pn--;
10862         while (pn > 0) {
10863             /* skip positions before number pn */
10864             if (fgets(line, MSG_SIZ, f) == NULL) {
10865                 Reset(TRUE, TRUE);
10866                 DisplayError(_("Position not found in file"), 0);
10867                 return FALSE;
10868             }
10869             if (fenMode || line[0] == '#') pn--;
10870         }
10871     }
10872
10873     if (fenMode) {
10874         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10875             DisplayError(_("Bad FEN position in file"), 0);
10876             return FALSE;
10877         }
10878     } else {
10879         (void) fgets(line, MSG_SIZ, f);
10880         (void) fgets(line, MSG_SIZ, f);
10881
10882         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10883             (void) fgets(line, MSG_SIZ, f);
10884             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10885                 if (*p == ' ')
10886                   continue;
10887                 initial_position[i][j++] = CharToPiece(*p);
10888             }
10889         }
10890
10891         blackPlaysFirst = FALSE;
10892         if (!feof(f)) {
10893             (void) fgets(line, MSG_SIZ, f);
10894             if (strncmp(line, "black", strlen("black"))==0)
10895               blackPlaysFirst = TRUE;
10896         }
10897     }
10898     startedFromSetupPosition = TRUE;
10899
10900     SendToProgram("force\n", &first);
10901     CopyBoard(boards[0], initial_position);
10902     if (blackPlaysFirst) {
10903         currentMove = forwardMostMove = backwardMostMove = 1;
10904         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10905         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10906         CopyBoard(boards[1], initial_position);
10907         DisplayMessage("", _("Black to play"));
10908     } else {
10909         currentMove = forwardMostMove = backwardMostMove = 0;
10910         DisplayMessage("", _("White to play"));
10911     }
10912     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10913     SendBoard(&first, forwardMostMove);
10914     if (appData.debugMode) {
10915 int i, j;
10916   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10917   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10918         fprintf(debugFP, "Load Position\n");
10919     }
10920
10921     if (positionNumber > 1) {
10922       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10923         DisplayTitle(line);
10924     } else {
10925         DisplayTitle(title);
10926     }
10927     gameMode = EditGame;
10928     ModeHighlight();
10929     ResetClocks();
10930     timeRemaining[0][1] = whiteTimeRemaining;
10931     timeRemaining[1][1] = blackTimeRemaining;
10932     DrawPosition(FALSE, boards[currentMove]);
10933
10934     return TRUE;
10935 }
10936
10937
10938 void
10939 CopyPlayerNameIntoFileName(dest, src)
10940      char **dest, *src;
10941 {
10942     while (*src != NULLCHAR && *src != ',') {
10943         if (*src == ' ') {
10944             *(*dest)++ = '_';
10945             src++;
10946         } else {
10947             *(*dest)++ = *src++;
10948         }
10949     }
10950 }
10951
10952 char *DefaultFileName(ext)
10953      char *ext;
10954 {
10955     static char def[MSG_SIZ];
10956     char *p;
10957
10958     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10959         p = def;
10960         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10961         *p++ = '-';
10962         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10963         *p++ = '.';
10964         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10965     } else {
10966         def[0] = NULLCHAR;
10967     }
10968     return def;
10969 }
10970
10971 /* Save the current game to the given file */
10972 int
10973 SaveGameToFile(filename, append)
10974      char *filename;
10975      int append;
10976 {
10977     FILE *f;
10978     char buf[MSG_SIZ];
10979
10980     if (strcmp(filename, "-") == 0) {
10981         return SaveGame(stdout, 0, NULL);
10982     } else {
10983         f = fopen(filename, append ? "a" : "w");
10984         if (f == NULL) {
10985             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10986             DisplayError(buf, errno);
10987             return FALSE;
10988         } else {
10989             return SaveGame(f, 0, NULL);
10990         }
10991     }
10992 }
10993
10994 char *
10995 SavePart(str)
10996      char *str;
10997 {
10998     static char buf[MSG_SIZ];
10999     char *p;
11000
11001     p = strchr(str, ' ');
11002     if (p == NULL) return str;
11003     strncpy(buf, str, p - str);
11004     buf[p - str] = NULLCHAR;
11005     return buf;
11006 }
11007
11008 #define PGN_MAX_LINE 75
11009
11010 #define PGN_SIDE_WHITE  0
11011 #define PGN_SIDE_BLACK  1
11012
11013 /* [AS] */
11014 static int FindFirstMoveOutOfBook( int side )
11015 {
11016     int result = -1;
11017
11018     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11019         int index = backwardMostMove;
11020         int has_book_hit = 0;
11021
11022         if( (index % 2) != side ) {
11023             index++;
11024         }
11025
11026         while( index < forwardMostMove ) {
11027             /* Check to see if engine is in book */
11028             int depth = pvInfoList[index].depth;
11029             int score = pvInfoList[index].score;
11030             int in_book = 0;
11031
11032             if( depth <= 2 ) {
11033                 in_book = 1;
11034             }
11035             else if( score == 0 && depth == 63 ) {
11036                 in_book = 1; /* Zappa */
11037             }
11038             else if( score == 2 && depth == 99 ) {
11039                 in_book = 1; /* Abrok */
11040             }
11041
11042             has_book_hit += in_book;
11043
11044             if( ! in_book ) {
11045                 result = index;
11046
11047                 break;
11048             }
11049
11050             index += 2;
11051         }
11052     }
11053
11054     return result;
11055 }
11056
11057 /* [AS] */
11058 void GetOutOfBookInfo( char * buf )
11059 {
11060     int oob[2];
11061     int i;
11062     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11063
11064     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11065     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11066
11067     *buf = '\0';
11068
11069     if( oob[0] >= 0 || oob[1] >= 0 ) {
11070         for( i=0; i<2; i++ ) {
11071             int idx = oob[i];
11072
11073             if( idx >= 0 ) {
11074                 if( i > 0 && oob[0] >= 0 ) {
11075                     strcat( buf, "   " );
11076                 }
11077
11078                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11079                 sprintf( buf+strlen(buf), "%s%.2f",
11080                     pvInfoList[idx].score >= 0 ? "+" : "",
11081                     pvInfoList[idx].score / 100.0 );
11082             }
11083         }
11084     }
11085 }
11086
11087 /* Save game in PGN style and close the file */
11088 int
11089 SaveGamePGN(f)
11090      FILE *f;
11091 {
11092     int i, offset, linelen, newblock;
11093     time_t tm;
11094 //    char *movetext;
11095     char numtext[32];
11096     int movelen, numlen, blank;
11097     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11098
11099     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11100
11101     tm = time((time_t *) NULL);
11102
11103     PrintPGNTags(f, &gameInfo);
11104
11105     if (backwardMostMove > 0 || startedFromSetupPosition) {
11106         char *fen = PositionToFEN(backwardMostMove, NULL);
11107         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11108         fprintf(f, "\n{--------------\n");
11109         PrintPosition(f, backwardMostMove);
11110         fprintf(f, "--------------}\n");
11111         free(fen);
11112     }
11113     else {
11114         /* [AS] Out of book annotation */
11115         if( appData.saveOutOfBookInfo ) {
11116             char buf[64];
11117
11118             GetOutOfBookInfo( buf );
11119
11120             if( buf[0] != '\0' ) {
11121                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11122             }
11123         }
11124
11125         fprintf(f, "\n");
11126     }
11127
11128     i = backwardMostMove;
11129     linelen = 0;
11130     newblock = TRUE;
11131
11132     while (i < forwardMostMove) {
11133         /* Print comments preceding this move */
11134         if (commentList[i] != NULL) {
11135             if (linelen > 0) fprintf(f, "\n");
11136             fprintf(f, "%s", commentList[i]);
11137             linelen = 0;
11138             newblock = TRUE;
11139         }
11140
11141         /* Format move number */
11142         if ((i % 2) == 0)
11143           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11144         else
11145           if (newblock)
11146             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11147           else
11148             numtext[0] = NULLCHAR;
11149
11150         numlen = strlen(numtext);
11151         newblock = FALSE;
11152
11153         /* Print move number */
11154         blank = linelen > 0 && numlen > 0;
11155         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11156             fprintf(f, "\n");
11157             linelen = 0;
11158             blank = 0;
11159         }
11160         if (blank) {
11161             fprintf(f, " ");
11162             linelen++;
11163         }
11164         fprintf(f, "%s", numtext);
11165         linelen += numlen;
11166
11167         /* Get move */
11168         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11169         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11170
11171         /* Print move */
11172         blank = linelen > 0 && movelen > 0;
11173         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11174             fprintf(f, "\n");
11175             linelen = 0;
11176             blank = 0;
11177         }
11178         if (blank) {
11179             fprintf(f, " ");
11180             linelen++;
11181         }
11182         fprintf(f, "%s", move_buffer);
11183         linelen += movelen;
11184
11185         /* [AS] Add PV info if present */
11186         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11187             /* [HGM] add time */
11188             char buf[MSG_SIZ]; int seconds;
11189
11190             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11191
11192             if( seconds <= 0)
11193               buf[0] = 0;
11194             else
11195               if( seconds < 30 )
11196                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11197               else
11198                 {
11199                   seconds = (seconds + 4)/10; // round to full seconds
11200                   if( seconds < 60 )
11201                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11202                   else
11203                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11204                 }
11205
11206             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11207                       pvInfoList[i].score >= 0 ? "+" : "",
11208                       pvInfoList[i].score / 100.0,
11209                       pvInfoList[i].depth,
11210                       buf );
11211
11212             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11213
11214             /* Print score/depth */
11215             blank = linelen > 0 && movelen > 0;
11216             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11217                 fprintf(f, "\n");
11218                 linelen = 0;
11219                 blank = 0;
11220             }
11221             if (blank) {
11222                 fprintf(f, " ");
11223                 linelen++;
11224             }
11225             fprintf(f, "%s", move_buffer);
11226             linelen += movelen;
11227         }
11228
11229         i++;
11230     }
11231
11232     /* Start a new line */
11233     if (linelen > 0) fprintf(f, "\n");
11234
11235     /* Print comments after last move */
11236     if (commentList[i] != NULL) {
11237         fprintf(f, "%s\n", commentList[i]);
11238     }
11239
11240     /* Print result */
11241     if (gameInfo.resultDetails != NULL &&
11242         gameInfo.resultDetails[0] != NULLCHAR) {
11243         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11244                 PGNResult(gameInfo.result));
11245     } else {
11246         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11247     }
11248
11249     fclose(f);
11250     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11251     return TRUE;
11252 }
11253
11254 /* Save game in old style and close the file */
11255 int
11256 SaveGameOldStyle(f)
11257      FILE *f;
11258 {
11259     int i, offset;
11260     time_t tm;
11261
11262     tm = time((time_t *) NULL);
11263
11264     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11265     PrintOpponents(f);
11266
11267     if (backwardMostMove > 0 || startedFromSetupPosition) {
11268         fprintf(f, "\n[--------------\n");
11269         PrintPosition(f, backwardMostMove);
11270         fprintf(f, "--------------]\n");
11271     } else {
11272         fprintf(f, "\n");
11273     }
11274
11275     i = backwardMostMove;
11276     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11277
11278     while (i < forwardMostMove) {
11279         if (commentList[i] != NULL) {
11280             fprintf(f, "[%s]\n", commentList[i]);
11281         }
11282
11283         if ((i % 2) == 1) {
11284             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11285             i++;
11286         } else {
11287             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11288             i++;
11289             if (commentList[i] != NULL) {
11290                 fprintf(f, "\n");
11291                 continue;
11292             }
11293             if (i >= forwardMostMove) {
11294                 fprintf(f, "\n");
11295                 break;
11296             }
11297             fprintf(f, "%s\n", parseList[i]);
11298             i++;
11299         }
11300     }
11301
11302     if (commentList[i] != NULL) {
11303         fprintf(f, "[%s]\n", commentList[i]);
11304     }
11305
11306     /* This isn't really the old style, but it's close enough */
11307     if (gameInfo.resultDetails != NULL &&
11308         gameInfo.resultDetails[0] != NULLCHAR) {
11309         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11310                 gameInfo.resultDetails);
11311     } else {
11312         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11313     }
11314
11315     fclose(f);
11316     return TRUE;
11317 }
11318
11319 /* Save the current game to open file f and close the file */
11320 int
11321 SaveGame(f, dummy, dummy2)
11322      FILE *f;
11323      int dummy;
11324      char *dummy2;
11325 {
11326     if (gameMode == EditPosition) EditPositionDone(TRUE);
11327     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11328     if (appData.oldSaveStyle)
11329       return SaveGameOldStyle(f);
11330     else
11331       return SaveGamePGN(f);
11332 }
11333
11334 /* Save the current position to the given file */
11335 int
11336 SavePositionToFile(filename)
11337      char *filename;
11338 {
11339     FILE *f;
11340     char buf[MSG_SIZ];
11341
11342     if (strcmp(filename, "-") == 0) {
11343         return SavePosition(stdout, 0, NULL);
11344     } else {
11345         f = fopen(filename, "a");
11346         if (f == NULL) {
11347             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11348             DisplayError(buf, errno);
11349             return FALSE;
11350         } else {
11351             SavePosition(f, 0, NULL);
11352             return TRUE;
11353         }
11354     }
11355 }
11356
11357 /* Save the current position to the given open file and close the file */
11358 int
11359 SavePosition(f, dummy, dummy2)
11360      FILE *f;
11361      int dummy;
11362      char *dummy2;
11363 {
11364     time_t tm;
11365     char *fen;
11366
11367     if (gameMode == EditPosition) EditPositionDone(TRUE);
11368     if (appData.oldSaveStyle) {
11369         tm = time((time_t *) NULL);
11370
11371         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11372         PrintOpponents(f);
11373         fprintf(f, "[--------------\n");
11374         PrintPosition(f, currentMove);
11375         fprintf(f, "--------------]\n");
11376     } else {
11377         fen = PositionToFEN(currentMove, NULL);
11378         fprintf(f, "%s\n", fen);
11379         free(fen);
11380     }
11381     fclose(f);
11382     return TRUE;
11383 }
11384
11385 void
11386 ReloadCmailMsgEvent(unregister)
11387      int unregister;
11388 {
11389 #if !WIN32
11390     static char *inFilename = NULL;
11391     static char *outFilename;
11392     int i;
11393     struct stat inbuf, outbuf;
11394     int status;
11395
11396     /* Any registered moves are unregistered if unregister is set, */
11397     /* i.e. invoked by the signal handler */
11398     if (unregister) {
11399         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11400             cmailMoveRegistered[i] = FALSE;
11401             if (cmailCommentList[i] != NULL) {
11402                 free(cmailCommentList[i]);
11403                 cmailCommentList[i] = NULL;
11404             }
11405         }
11406         nCmailMovesRegistered = 0;
11407     }
11408
11409     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11410         cmailResult[i] = CMAIL_NOT_RESULT;
11411     }
11412     nCmailResults = 0;
11413
11414     if (inFilename == NULL) {
11415         /* Because the filenames are static they only get malloced once  */
11416         /* and they never get freed                                      */
11417         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11418         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11419
11420         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11421         sprintf(outFilename, "%s.out", appData.cmailGameName);
11422     }
11423
11424     status = stat(outFilename, &outbuf);
11425     if (status < 0) {
11426         cmailMailedMove = FALSE;
11427     } else {
11428         status = stat(inFilename, &inbuf);
11429         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11430     }
11431
11432     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11433        counts the games, notes how each one terminated, etc.
11434
11435        It would be nice to remove this kludge and instead gather all
11436        the information while building the game list.  (And to keep it
11437        in the game list nodes instead of having a bunch of fixed-size
11438        parallel arrays.)  Note this will require getting each game's
11439        termination from the PGN tags, as the game list builder does
11440        not process the game moves.  --mann
11441        */
11442     cmailMsgLoaded = TRUE;
11443     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11444
11445     /* Load first game in the file or popup game menu */
11446     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11447
11448 #endif /* !WIN32 */
11449     return;
11450 }
11451
11452 int
11453 RegisterMove()
11454 {
11455     FILE *f;
11456     char string[MSG_SIZ];
11457
11458     if (   cmailMailedMove
11459         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11460         return TRUE;            /* Allow free viewing  */
11461     }
11462
11463     /* Unregister move to ensure that we don't leave RegisterMove        */
11464     /* with the move registered when the conditions for registering no   */
11465     /* longer hold                                                       */
11466     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11467         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11468         nCmailMovesRegistered --;
11469
11470         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11471           {
11472               free(cmailCommentList[lastLoadGameNumber - 1]);
11473               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11474           }
11475     }
11476
11477     if (cmailOldMove == -1) {
11478         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11479         return FALSE;
11480     }
11481
11482     if (currentMove > cmailOldMove + 1) {
11483         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11484         return FALSE;
11485     }
11486
11487     if (currentMove < cmailOldMove) {
11488         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11489         return FALSE;
11490     }
11491
11492     if (forwardMostMove > currentMove) {
11493         /* Silently truncate extra moves */
11494         TruncateGame();
11495     }
11496
11497     if (   (currentMove == cmailOldMove + 1)
11498         || (   (currentMove == cmailOldMove)
11499             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11500                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11501         if (gameInfo.result != GameUnfinished) {
11502             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11503         }
11504
11505         if (commentList[currentMove] != NULL) {
11506             cmailCommentList[lastLoadGameNumber - 1]
11507               = StrSave(commentList[currentMove]);
11508         }
11509         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11510
11511         if (appData.debugMode)
11512           fprintf(debugFP, "Saving %s for game %d\n",
11513                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11514
11515         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11516
11517         f = fopen(string, "w");
11518         if (appData.oldSaveStyle) {
11519             SaveGameOldStyle(f); /* also closes the file */
11520
11521             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11522             f = fopen(string, "w");
11523             SavePosition(f, 0, NULL); /* also closes the file */
11524         } else {
11525             fprintf(f, "{--------------\n");
11526             PrintPosition(f, currentMove);
11527             fprintf(f, "--------------}\n\n");
11528
11529             SaveGame(f, 0, NULL); /* also closes the file*/
11530         }
11531
11532         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11533         nCmailMovesRegistered ++;
11534     } else if (nCmailGames == 1) {
11535         DisplayError(_("You have not made a move yet"), 0);
11536         return FALSE;
11537     }
11538
11539     return TRUE;
11540 }
11541
11542 void
11543 MailMoveEvent()
11544 {
11545 #if !WIN32
11546     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11547     FILE *commandOutput;
11548     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11549     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11550     int nBuffers;
11551     int i;
11552     int archived;
11553     char *arcDir;
11554
11555     if (! cmailMsgLoaded) {
11556         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11557         return;
11558     }
11559
11560     if (nCmailGames == nCmailResults) {
11561         DisplayError(_("No unfinished games"), 0);
11562         return;
11563     }
11564
11565 #if CMAIL_PROHIBIT_REMAIL
11566     if (cmailMailedMove) {
11567       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);
11568         DisplayError(msg, 0);
11569         return;
11570     }
11571 #endif
11572
11573     if (! (cmailMailedMove || RegisterMove())) return;
11574
11575     if (   cmailMailedMove
11576         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11577       snprintf(string, MSG_SIZ, partCommandString,
11578                appData.debugMode ? " -v" : "", appData.cmailGameName);
11579         commandOutput = popen(string, "r");
11580
11581         if (commandOutput == NULL) {
11582             DisplayError(_("Failed to invoke cmail"), 0);
11583         } else {
11584             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11585                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11586             }
11587             if (nBuffers > 1) {
11588                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11589                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11590                 nBytes = MSG_SIZ - 1;
11591             } else {
11592                 (void) memcpy(msg, buffer, nBytes);
11593             }
11594             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11595
11596             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11597                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11598
11599                 archived = TRUE;
11600                 for (i = 0; i < nCmailGames; i ++) {
11601                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11602                         archived = FALSE;
11603                     }
11604                 }
11605                 if (   archived
11606                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11607                         != NULL)) {
11608                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11609                            arcDir,
11610                            appData.cmailGameName,
11611                            gameInfo.date);
11612                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11613                     cmailMsgLoaded = FALSE;
11614                 }
11615             }
11616
11617             DisplayInformation(msg);
11618             pclose(commandOutput);
11619         }
11620     } else {
11621         if ((*cmailMsg) != '\0') {
11622             DisplayInformation(cmailMsg);
11623         }
11624     }
11625
11626     return;
11627 #endif /* !WIN32 */
11628 }
11629
11630 char *
11631 CmailMsg()
11632 {
11633 #if WIN32
11634     return NULL;
11635 #else
11636     int  prependComma = 0;
11637     char number[5];
11638     char string[MSG_SIZ];       /* Space for game-list */
11639     int  i;
11640
11641     if (!cmailMsgLoaded) return "";
11642
11643     if (cmailMailedMove) {
11644       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11645     } else {
11646         /* Create a list of games left */
11647       snprintf(string, MSG_SIZ, "[");
11648         for (i = 0; i < nCmailGames; i ++) {
11649             if (! (   cmailMoveRegistered[i]
11650                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11651                 if (prependComma) {
11652                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11653                 } else {
11654                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11655                     prependComma = 1;
11656                 }
11657
11658                 strcat(string, number);
11659             }
11660         }
11661         strcat(string, "]");
11662
11663         if (nCmailMovesRegistered + nCmailResults == 0) {
11664             switch (nCmailGames) {
11665               case 1:
11666                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11667                 break;
11668
11669               case 2:
11670                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11671                 break;
11672
11673               default:
11674                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11675                          nCmailGames);
11676                 break;
11677             }
11678         } else {
11679             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11680               case 1:
11681                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11682                          string);
11683                 break;
11684
11685               case 0:
11686                 if (nCmailResults == nCmailGames) {
11687                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11688                 } else {
11689                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11690                 }
11691                 break;
11692
11693               default:
11694                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11695                          string);
11696             }
11697         }
11698     }
11699     return cmailMsg;
11700 #endif /* WIN32 */
11701 }
11702
11703 void
11704 ResetGameEvent()
11705 {
11706     if (gameMode == Training)
11707       SetTrainingModeOff();
11708
11709     Reset(TRUE, TRUE);
11710     cmailMsgLoaded = FALSE;
11711     if (appData.icsActive) {
11712       SendToICS(ics_prefix);
11713       SendToICS("refresh\n");
11714     }
11715 }
11716
11717 void
11718 ExitEvent(status)
11719      int status;
11720 {
11721     exiting++;
11722     if (exiting > 2) {
11723       /* Give up on clean exit */
11724       exit(status);
11725     }
11726     if (exiting > 1) {
11727       /* Keep trying for clean exit */
11728       return;
11729     }
11730
11731     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11732
11733     if (telnetISR != NULL) {
11734       RemoveInputSource(telnetISR);
11735     }
11736     if (icsPR != NoProc) {
11737       DestroyChildProcess(icsPR, TRUE);
11738     }
11739
11740     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11741     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11742
11743     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11744     /* make sure this other one finishes before killing it!                  */
11745     if(endingGame) { int count = 0;
11746         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11747         while(endingGame && count++ < 10) DoSleep(1);
11748         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11749     }
11750
11751     /* Kill off chess programs */
11752     if (first.pr != NoProc) {
11753         ExitAnalyzeMode();
11754
11755         DoSleep( appData.delayBeforeQuit );
11756         SendToProgram("quit\n", &first);
11757         DoSleep( appData.delayAfterQuit );
11758         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11759     }
11760     if (second.pr != NoProc) {
11761         DoSleep( appData.delayBeforeQuit );
11762         SendToProgram("quit\n", &second);
11763         DoSleep( appData.delayAfterQuit );
11764         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11765     }
11766     if (first.isr != NULL) {
11767         RemoveInputSource(first.isr);
11768     }
11769     if (second.isr != NULL) {
11770         RemoveInputSource(second.isr);
11771     }
11772
11773     ShutDownFrontEnd();
11774     exit(status);
11775 }
11776
11777 void
11778 PauseEvent()
11779 {
11780     if (appData.debugMode)
11781         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11782     if (pausing) {
11783         pausing = FALSE;
11784         ModeHighlight();
11785         if (gameMode == MachinePlaysWhite ||
11786             gameMode == MachinePlaysBlack) {
11787             StartClocks();
11788         } else {
11789             DisplayBothClocks();
11790         }
11791         if (gameMode == PlayFromGameFile) {
11792             if (appData.timeDelay >= 0)
11793                 AutoPlayGameLoop();
11794         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11795             Reset(FALSE, TRUE);
11796             SendToICS(ics_prefix);
11797             SendToICS("refresh\n");
11798         } else if (currentMove < forwardMostMove) {
11799             ForwardInner(forwardMostMove);
11800         }
11801         pauseExamInvalid = FALSE;
11802     } else {
11803         switch (gameMode) {
11804           default:
11805             return;
11806           case IcsExamining:
11807             pauseExamForwardMostMove = forwardMostMove;
11808             pauseExamInvalid = FALSE;
11809             /* fall through */
11810           case IcsObserving:
11811           case IcsPlayingWhite:
11812           case IcsPlayingBlack:
11813             pausing = TRUE;
11814             ModeHighlight();
11815             return;
11816           case PlayFromGameFile:
11817             (void) StopLoadGameTimer();
11818             pausing = TRUE;
11819             ModeHighlight();
11820             break;
11821           case BeginningOfGame:
11822             if (appData.icsActive) return;
11823             /* else fall through */
11824           case MachinePlaysWhite:
11825           case MachinePlaysBlack:
11826           case TwoMachinesPlay:
11827             if (forwardMostMove == 0)
11828               return;           /* don't pause if no one has moved */
11829             if ((gameMode == MachinePlaysWhite &&
11830                  !WhiteOnMove(forwardMostMove)) ||
11831                 (gameMode == MachinePlaysBlack &&
11832                  WhiteOnMove(forwardMostMove))) {
11833                 StopClocks();
11834             }
11835             pausing = TRUE;
11836             ModeHighlight();
11837             break;
11838         }
11839     }
11840 }
11841
11842 void
11843 EditCommentEvent()
11844 {
11845     char title[MSG_SIZ];
11846
11847     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11848       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11849     } else {
11850       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11851                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11852                parseList[currentMove - 1]);
11853     }
11854
11855     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11856 }
11857
11858
11859 void
11860 EditTagsEvent()
11861 {
11862     char *tags = PGNTags(&gameInfo);
11863     EditTagsPopUp(tags, NULL);
11864     free(tags);
11865 }
11866
11867 void
11868 AnalyzeModeEvent()
11869 {
11870     if (appData.noChessProgram || gameMode == AnalyzeMode)
11871       return;
11872
11873     if (gameMode != AnalyzeFile) {
11874         if (!appData.icsEngineAnalyze) {
11875                EditGameEvent();
11876                if (gameMode != EditGame) return;
11877         }
11878         ResurrectChessProgram();
11879         SendToProgram("analyze\n", &first);
11880         first.analyzing = TRUE;
11881         /*first.maybeThinking = TRUE;*/
11882         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11883         EngineOutputPopUp();
11884     }
11885     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11886     pausing = FALSE;
11887     ModeHighlight();
11888     SetGameInfo();
11889
11890     StartAnalysisClock();
11891     GetTimeMark(&lastNodeCountTime);
11892     lastNodeCount = 0;
11893 }
11894
11895 void
11896 AnalyzeFileEvent()
11897 {
11898     if (appData.noChessProgram || gameMode == AnalyzeFile)
11899       return;
11900
11901     if (gameMode != AnalyzeMode) {
11902         EditGameEvent();
11903         if (gameMode != EditGame) return;
11904         ResurrectChessProgram();
11905         SendToProgram("analyze\n", &first);
11906         first.analyzing = TRUE;
11907         /*first.maybeThinking = TRUE;*/
11908         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11909         EngineOutputPopUp();
11910     }
11911     gameMode = AnalyzeFile;
11912     pausing = FALSE;
11913     ModeHighlight();
11914     SetGameInfo();
11915
11916     StartAnalysisClock();
11917     GetTimeMark(&lastNodeCountTime);
11918     lastNodeCount = 0;
11919 }
11920
11921 void
11922 MachineWhiteEvent()
11923 {
11924     char buf[MSG_SIZ];
11925     char *bookHit = NULL;
11926
11927     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11928       return;
11929
11930
11931     if (gameMode == PlayFromGameFile ||
11932         gameMode == TwoMachinesPlay  ||
11933         gameMode == Training         ||
11934         gameMode == AnalyzeMode      ||
11935         gameMode == EndOfGame)
11936         EditGameEvent();
11937
11938     if (gameMode == EditPosition)
11939         EditPositionDone(TRUE);
11940
11941     if (!WhiteOnMove(currentMove)) {
11942         DisplayError(_("It is not White's turn"), 0);
11943         return;
11944     }
11945
11946     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11947       ExitAnalyzeMode();
11948
11949     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11950         gameMode == AnalyzeFile)
11951         TruncateGame();
11952
11953     ResurrectChessProgram();    /* in case it isn't running */
11954     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11955         gameMode = MachinePlaysWhite;
11956         ResetClocks();
11957     } else
11958     gameMode = MachinePlaysWhite;
11959     pausing = FALSE;
11960     ModeHighlight();
11961     SetGameInfo();
11962     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11963     DisplayTitle(buf);
11964     if (first.sendName) {
11965       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11966       SendToProgram(buf, &first);
11967     }
11968     if (first.sendTime) {
11969       if (first.useColors) {
11970         SendToProgram("black\n", &first); /*gnu kludge*/
11971       }
11972       SendTimeRemaining(&first, TRUE);
11973     }
11974     if (first.useColors) {
11975       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11976     }
11977     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11978     SetMachineThinkingEnables();
11979     first.maybeThinking = TRUE;
11980     StartClocks();
11981     firstMove = FALSE;
11982
11983     if (appData.autoFlipView && !flipView) {
11984       flipView = !flipView;
11985       DrawPosition(FALSE, NULL);
11986       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11987     }
11988
11989     if(bookHit) { // [HGM] book: simulate book reply
11990         static char bookMove[MSG_SIZ]; // a bit generous?
11991
11992         programStats.nodes = programStats.depth = programStats.time =
11993         programStats.score = programStats.got_only_move = 0;
11994         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11995
11996         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11997         strcat(bookMove, bookHit);
11998         HandleMachineMove(bookMove, &first);
11999     }
12000 }
12001
12002 void
12003 MachineBlackEvent()
12004 {
12005   char buf[MSG_SIZ];
12006   char *bookHit = NULL;
12007
12008     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12009         return;
12010
12011
12012     if (gameMode == PlayFromGameFile ||
12013         gameMode == TwoMachinesPlay  ||
12014         gameMode == Training         ||
12015         gameMode == AnalyzeMode      ||
12016         gameMode == EndOfGame)
12017         EditGameEvent();
12018
12019     if (gameMode == EditPosition)
12020         EditPositionDone(TRUE);
12021
12022     if (WhiteOnMove(currentMove)) {
12023         DisplayError(_("It is not Black's turn"), 0);
12024         return;
12025     }
12026
12027     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12028       ExitAnalyzeMode();
12029
12030     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12031         gameMode == AnalyzeFile)
12032         TruncateGame();
12033
12034     ResurrectChessProgram();    /* in case it isn't running */
12035     gameMode = MachinePlaysBlack;
12036     pausing = FALSE;
12037     ModeHighlight();
12038     SetGameInfo();
12039     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12040     DisplayTitle(buf);
12041     if (first.sendName) {
12042       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12043       SendToProgram(buf, &first);
12044     }
12045     if (first.sendTime) {
12046       if (first.useColors) {
12047         SendToProgram("white\n", &first); /*gnu kludge*/
12048       }
12049       SendTimeRemaining(&first, FALSE);
12050     }
12051     if (first.useColors) {
12052       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12053     }
12054     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12055     SetMachineThinkingEnables();
12056     first.maybeThinking = TRUE;
12057     StartClocks();
12058
12059     if (appData.autoFlipView && flipView) {
12060       flipView = !flipView;
12061       DrawPosition(FALSE, NULL);
12062       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12063     }
12064     if(bookHit) { // [HGM] book: simulate book reply
12065         static char bookMove[MSG_SIZ]; // a bit generous?
12066
12067         programStats.nodes = programStats.depth = programStats.time =
12068         programStats.score = programStats.got_only_move = 0;
12069         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12070
12071         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12072         strcat(bookMove, bookHit);
12073         HandleMachineMove(bookMove, &first);
12074     }
12075 }
12076
12077
12078 void
12079 DisplayTwoMachinesTitle()
12080 {
12081     char buf[MSG_SIZ];
12082     if (appData.matchGames > 0) {
12083         if (first.twoMachinesColor[0] == 'w') {
12084           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12085                    gameInfo.white, gameInfo.black,
12086                    first.matchWins, second.matchWins,
12087                    matchGame - 1 - (first.matchWins + second.matchWins));
12088         } else {
12089           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12090                    gameInfo.white, gameInfo.black,
12091                    second.matchWins, first.matchWins,
12092                    matchGame - 1 - (first.matchWins + second.matchWins));
12093         }
12094     } else {
12095       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12096     }
12097     DisplayTitle(buf);
12098 }
12099
12100 void
12101 SettingsMenuIfReady()
12102 {
12103   if (second.lastPing != second.lastPong) {
12104     DisplayMessage("", _("Waiting for second chess program"));
12105     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12106     return;
12107   }
12108   ThawUI();
12109   DisplayMessage("", "");
12110   SettingsPopUp(&second);
12111 }
12112
12113 int
12114 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12115 {
12116     char buf[MSG_SIZ];
12117     if (cps->pr == NULL) {
12118         StartChessProgram(cps);
12119         if (cps->protocolVersion == 1) {
12120           retry();
12121         } else {
12122           /* kludge: allow timeout for initial "feature" command */
12123           FreezeUI();
12124           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12125           DisplayMessage("", buf);
12126           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12127         }
12128         return 1;
12129     }
12130     return 0;
12131 }
12132
12133 void
12134 TwoMachinesEvent P((void))
12135 {
12136     int i;
12137     char buf[MSG_SIZ];
12138     ChessProgramState *onmove;
12139     char *bookHit = NULL;
12140     static int stalling = 0;
12141
12142     if (appData.noChessProgram) return;
12143
12144     switch (gameMode) {
12145       case TwoMachinesPlay:
12146         return;
12147       case MachinePlaysWhite:
12148       case MachinePlaysBlack:
12149         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12150             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12151             return;
12152         }
12153         /* fall through */
12154       case BeginningOfGame:
12155       case PlayFromGameFile:
12156       case EndOfGame:
12157         EditGameEvent();
12158         if (gameMode != EditGame) return;
12159         break;
12160       case EditPosition:
12161         EditPositionDone(TRUE);
12162         break;
12163       case AnalyzeMode:
12164       case AnalyzeFile:
12165         ExitAnalyzeMode();
12166         break;
12167       case EditGame:
12168       default:
12169         break;
12170     }
12171
12172 //    forwardMostMove = currentMove;
12173     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12174     ResurrectChessProgram();    /* in case first program isn't running */
12175
12176     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12177     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12178       DisplayMessage("", _("Waiting for first chess program"));
12179       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12180       return;
12181     }
12182     if(!stalling) {
12183       InitChessProgram(&second, FALSE);
12184       SendToProgram("force\n", &second);
12185     }
12186     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12187       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12188       stalling = 1;
12189       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12190       return;
12191     }
12192     stalling = 0;
12193     DisplayMessage("", "");
12194     if (startedFromSetupPosition) {
12195         SendBoard(&second, backwardMostMove);
12196     if (appData.debugMode) {
12197         fprintf(debugFP, "Two Machines\n");
12198     }
12199     }
12200     for (i = backwardMostMove; i < forwardMostMove; i++) {
12201         SendMoveToProgram(i, &second);
12202     }
12203
12204     gameMode = TwoMachinesPlay;
12205     pausing = FALSE;
12206     ModeHighlight();
12207     SetGameInfo();
12208     DisplayTwoMachinesTitle();
12209     firstMove = TRUE;
12210     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12211         onmove = &first;
12212     } else {
12213         onmove = &second;
12214     }
12215
12216     SendToProgram(first.computerString, &first);
12217     if (first.sendName) {
12218       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12219       SendToProgram(buf, &first);
12220     }
12221     SendToProgram(second.computerString, &second);
12222     if (second.sendName) {
12223       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12224       SendToProgram(buf, &second);
12225     }
12226
12227     ResetClocks();
12228     if (!first.sendTime || !second.sendTime) {
12229         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12230         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12231     }
12232     if (onmove->sendTime) {
12233       if (onmove->useColors) {
12234         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12235       }
12236       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12237     }
12238     if (onmove->useColors) {
12239       SendToProgram(onmove->twoMachinesColor, onmove);
12240     }
12241     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12242 //    SendToProgram("go\n", onmove);
12243     onmove->maybeThinking = TRUE;
12244     SetMachineThinkingEnables();
12245
12246     StartClocks();
12247
12248     if(bookHit) { // [HGM] book: simulate book reply
12249         static char bookMove[MSG_SIZ]; // a bit generous?
12250
12251         programStats.nodes = programStats.depth = programStats.time =
12252         programStats.score = programStats.got_only_move = 0;
12253         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12254
12255         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12256         strcat(bookMove, bookHit);
12257         savedMessage = bookMove; // args for deferred call
12258         savedState = onmove;
12259         ScheduleDelayedEvent(DeferredBookMove, 1);
12260     }
12261 }
12262
12263 void
12264 TrainingEvent()
12265 {
12266     if (gameMode == Training) {
12267       SetTrainingModeOff();
12268       gameMode = PlayFromGameFile;
12269       DisplayMessage("", _("Training mode off"));
12270     } else {
12271       gameMode = Training;
12272       animateTraining = appData.animate;
12273
12274       /* make sure we are not already at the end of the game */
12275       if (currentMove < forwardMostMove) {
12276         SetTrainingModeOn();
12277         DisplayMessage("", _("Training mode on"));
12278       } else {
12279         gameMode = PlayFromGameFile;
12280         DisplayError(_("Already at end of game"), 0);
12281       }
12282     }
12283     ModeHighlight();
12284 }
12285
12286 void
12287 IcsClientEvent()
12288 {
12289     if (!appData.icsActive) return;
12290     switch (gameMode) {
12291       case IcsPlayingWhite:
12292       case IcsPlayingBlack:
12293       case IcsObserving:
12294       case IcsIdle:
12295       case BeginningOfGame:
12296       case IcsExamining:
12297         return;
12298
12299       case EditGame:
12300         break;
12301
12302       case EditPosition:
12303         EditPositionDone(TRUE);
12304         break;
12305
12306       case AnalyzeMode:
12307       case AnalyzeFile:
12308         ExitAnalyzeMode();
12309         break;
12310
12311       default:
12312         EditGameEvent();
12313         break;
12314     }
12315
12316     gameMode = IcsIdle;
12317     ModeHighlight();
12318     return;
12319 }
12320
12321
12322 void
12323 EditGameEvent()
12324 {
12325     int i;
12326
12327     switch (gameMode) {
12328       case Training:
12329         SetTrainingModeOff();
12330         break;
12331       case MachinePlaysWhite:
12332       case MachinePlaysBlack:
12333       case BeginningOfGame:
12334         SendToProgram("force\n", &first);
12335         SetUserThinkingEnables();
12336         break;
12337       case PlayFromGameFile:
12338         (void) StopLoadGameTimer();
12339         if (gameFileFP != NULL) {
12340             gameFileFP = NULL;
12341         }
12342         break;
12343       case EditPosition:
12344         EditPositionDone(TRUE);
12345         break;
12346       case AnalyzeMode:
12347       case AnalyzeFile:
12348         ExitAnalyzeMode();
12349         SendToProgram("force\n", &first);
12350         break;
12351       case TwoMachinesPlay:
12352         GameEnds(EndOfFile, NULL, GE_PLAYER);
12353         ResurrectChessProgram();
12354         SetUserThinkingEnables();
12355         break;
12356       case EndOfGame:
12357         ResurrectChessProgram();
12358         break;
12359       case IcsPlayingBlack:
12360       case IcsPlayingWhite:
12361         DisplayError(_("Warning: You are still playing a game"), 0);
12362         break;
12363       case IcsObserving:
12364         DisplayError(_("Warning: You are still observing a game"), 0);
12365         break;
12366       case IcsExamining:
12367         DisplayError(_("Warning: You are still examining a game"), 0);
12368         break;
12369       case IcsIdle:
12370         break;
12371       case EditGame:
12372       default:
12373         return;
12374     }
12375
12376     pausing = FALSE;
12377     StopClocks();
12378     first.offeredDraw = second.offeredDraw = 0;
12379
12380     if (gameMode == PlayFromGameFile) {
12381         whiteTimeRemaining = timeRemaining[0][currentMove];
12382         blackTimeRemaining = timeRemaining[1][currentMove];
12383         DisplayTitle("");
12384     }
12385
12386     if (gameMode == MachinePlaysWhite ||
12387         gameMode == MachinePlaysBlack ||
12388         gameMode == TwoMachinesPlay ||
12389         gameMode == EndOfGame) {
12390         i = forwardMostMove;
12391         while (i > currentMove) {
12392             SendToProgram("undo\n", &first);
12393             i--;
12394         }
12395         whiteTimeRemaining = timeRemaining[0][currentMove];
12396         blackTimeRemaining = timeRemaining[1][currentMove];
12397         DisplayBothClocks();
12398         if (whiteFlag || blackFlag) {
12399             whiteFlag = blackFlag = 0;
12400         }
12401         DisplayTitle("");
12402     }
12403
12404     gameMode = EditGame;
12405     ModeHighlight();
12406     SetGameInfo();
12407 }
12408
12409
12410 void
12411 EditPositionEvent()
12412 {
12413     if (gameMode == EditPosition) {
12414         EditGameEvent();
12415         return;
12416     }
12417
12418     EditGameEvent();
12419     if (gameMode != EditGame) return;
12420
12421     gameMode = EditPosition;
12422     ModeHighlight();
12423     SetGameInfo();
12424     if (currentMove > 0)
12425       CopyBoard(boards[0], boards[currentMove]);
12426
12427     blackPlaysFirst = !WhiteOnMove(currentMove);
12428     ResetClocks();
12429     currentMove = forwardMostMove = backwardMostMove = 0;
12430     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12431     DisplayMove(-1);
12432 }
12433
12434 void
12435 ExitAnalyzeMode()
12436 {
12437     /* [DM] icsEngineAnalyze - possible call from other functions */
12438     if (appData.icsEngineAnalyze) {
12439         appData.icsEngineAnalyze = FALSE;
12440
12441         DisplayMessage("",_("Close ICS engine analyze..."));
12442     }
12443     if (first.analysisSupport && first.analyzing) {
12444       SendToProgram("exit\n", &first);
12445       first.analyzing = FALSE;
12446     }
12447     thinkOutput[0] = NULLCHAR;
12448 }
12449
12450 void
12451 EditPositionDone(Boolean fakeRights)
12452 {
12453     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12454
12455     startedFromSetupPosition = TRUE;
12456     InitChessProgram(&first, FALSE);
12457     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12458       boards[0][EP_STATUS] = EP_NONE;
12459       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12460     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12461         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12462         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12463       } else boards[0][CASTLING][2] = NoRights;
12464     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12465         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12466         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12467       } else boards[0][CASTLING][5] = NoRights;
12468     }
12469     SendToProgram("force\n", &first);
12470     if (blackPlaysFirst) {
12471         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12472         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12473         currentMove = forwardMostMove = backwardMostMove = 1;
12474         CopyBoard(boards[1], boards[0]);
12475     } else {
12476         currentMove = forwardMostMove = backwardMostMove = 0;
12477     }
12478     SendBoard(&first, forwardMostMove);
12479     if (appData.debugMode) {
12480         fprintf(debugFP, "EditPosDone\n");
12481     }
12482     DisplayTitle("");
12483     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12484     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12485     gameMode = EditGame;
12486     ModeHighlight();
12487     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12488     ClearHighlights(); /* [AS] */
12489 }
12490
12491 /* Pause for `ms' milliseconds */
12492 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12493 void
12494 TimeDelay(ms)
12495      long ms;
12496 {
12497     TimeMark m1, m2;
12498
12499     GetTimeMark(&m1);
12500     do {
12501         GetTimeMark(&m2);
12502     } while (SubtractTimeMarks(&m2, &m1) < ms);
12503 }
12504
12505 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12506 void
12507 SendMultiLineToICS(buf)
12508      char *buf;
12509 {
12510     char temp[MSG_SIZ+1], *p;
12511     int len;
12512
12513     len = strlen(buf);
12514     if (len > MSG_SIZ)
12515       len = MSG_SIZ;
12516
12517     strncpy(temp, buf, len);
12518     temp[len] = 0;
12519
12520     p = temp;
12521     while (*p) {
12522         if (*p == '\n' || *p == '\r')
12523           *p = ' ';
12524         ++p;
12525     }
12526
12527     strcat(temp, "\n");
12528     SendToICS(temp);
12529     SendToPlayer(temp, strlen(temp));
12530 }
12531
12532 void
12533 SetWhiteToPlayEvent()
12534 {
12535     if (gameMode == EditPosition) {
12536         blackPlaysFirst = FALSE;
12537         DisplayBothClocks();    /* works because currentMove is 0 */
12538     } else if (gameMode == IcsExamining) {
12539         SendToICS(ics_prefix);
12540         SendToICS("tomove white\n");
12541     }
12542 }
12543
12544 void
12545 SetBlackToPlayEvent()
12546 {
12547     if (gameMode == EditPosition) {
12548         blackPlaysFirst = TRUE;
12549         currentMove = 1;        /* kludge */
12550         DisplayBothClocks();
12551         currentMove = 0;
12552     } else if (gameMode == IcsExamining) {
12553         SendToICS(ics_prefix);
12554         SendToICS("tomove black\n");
12555     }
12556 }
12557
12558 void
12559 EditPositionMenuEvent(selection, x, y)
12560      ChessSquare selection;
12561      int x, y;
12562 {
12563     char buf[MSG_SIZ];
12564     ChessSquare piece = boards[0][y][x];
12565
12566     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12567
12568     switch (selection) {
12569       case ClearBoard:
12570         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12571             SendToICS(ics_prefix);
12572             SendToICS("bsetup clear\n");
12573         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12574             SendToICS(ics_prefix);
12575             SendToICS("clearboard\n");
12576         } else {
12577             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12578                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12579                 for (y = 0; y < BOARD_HEIGHT; y++) {
12580                     if (gameMode == IcsExamining) {
12581                         if (boards[currentMove][y][x] != EmptySquare) {
12582                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12583                                     AAA + x, ONE + y);
12584                             SendToICS(buf);
12585                         }
12586                     } else {
12587                         boards[0][y][x] = p;
12588                     }
12589                 }
12590             }
12591         }
12592         if (gameMode == EditPosition) {
12593             DrawPosition(FALSE, boards[0]);
12594         }
12595         break;
12596
12597       case WhitePlay:
12598         SetWhiteToPlayEvent();
12599         break;
12600
12601       case BlackPlay:
12602         SetBlackToPlayEvent();
12603         break;
12604
12605       case EmptySquare:
12606         if (gameMode == IcsExamining) {
12607             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12608             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12609             SendToICS(buf);
12610         } else {
12611             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12612                 if(x == BOARD_LEFT-2) {
12613                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12614                     boards[0][y][1] = 0;
12615                 } else
12616                 if(x == BOARD_RGHT+1) {
12617                     if(y >= gameInfo.holdingsSize) break;
12618                     boards[0][y][BOARD_WIDTH-2] = 0;
12619                 } else break;
12620             }
12621             boards[0][y][x] = EmptySquare;
12622             DrawPosition(FALSE, boards[0]);
12623         }
12624         break;
12625
12626       case PromotePiece:
12627         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12628            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12629             selection = (ChessSquare) (PROMOTED piece);
12630         } else if(piece == EmptySquare) selection = WhiteSilver;
12631         else selection = (ChessSquare)((int)piece - 1);
12632         goto defaultlabel;
12633
12634       case DemotePiece:
12635         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12636            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12637             selection = (ChessSquare) (DEMOTED piece);
12638         } else if(piece == EmptySquare) selection = BlackSilver;
12639         else selection = (ChessSquare)((int)piece + 1);
12640         goto defaultlabel;
12641
12642       case WhiteQueen:
12643       case BlackQueen:
12644         if(gameInfo.variant == VariantShatranj ||
12645            gameInfo.variant == VariantXiangqi  ||
12646            gameInfo.variant == VariantCourier  ||
12647            gameInfo.variant == VariantMakruk     )
12648             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12649         goto defaultlabel;
12650
12651       case WhiteKing:
12652       case BlackKing:
12653         if(gameInfo.variant == VariantXiangqi)
12654             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12655         if(gameInfo.variant == VariantKnightmate)
12656             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12657       default:
12658         defaultlabel:
12659         if (gameMode == IcsExamining) {
12660             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12661             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12662                      PieceToChar(selection), AAA + x, ONE + y);
12663             SendToICS(buf);
12664         } else {
12665             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12666                 int n;
12667                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12668                     n = PieceToNumber(selection - BlackPawn);
12669                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12670                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12671                     boards[0][BOARD_HEIGHT-1-n][1]++;
12672                 } else
12673                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12674                     n = PieceToNumber(selection);
12675                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12676                     boards[0][n][BOARD_WIDTH-1] = selection;
12677                     boards[0][n][BOARD_WIDTH-2]++;
12678                 }
12679             } else
12680             boards[0][y][x] = selection;
12681             DrawPosition(TRUE, boards[0]);
12682         }
12683         break;
12684     }
12685 }
12686
12687
12688 void
12689 DropMenuEvent(selection, x, y)
12690      ChessSquare selection;
12691      int x, y;
12692 {
12693     ChessMove moveType;
12694
12695     switch (gameMode) {
12696       case IcsPlayingWhite:
12697       case MachinePlaysBlack:
12698         if (!WhiteOnMove(currentMove)) {
12699             DisplayMoveError(_("It is Black's turn"));
12700             return;
12701         }
12702         moveType = WhiteDrop;
12703         break;
12704       case IcsPlayingBlack:
12705       case MachinePlaysWhite:
12706         if (WhiteOnMove(currentMove)) {
12707             DisplayMoveError(_("It is White's turn"));
12708             return;
12709         }
12710         moveType = BlackDrop;
12711         break;
12712       case EditGame:
12713         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12714         break;
12715       default:
12716         return;
12717     }
12718
12719     if (moveType == BlackDrop && selection < BlackPawn) {
12720       selection = (ChessSquare) ((int) selection
12721                                  + (int) BlackPawn - (int) WhitePawn);
12722     }
12723     if (boards[currentMove][y][x] != EmptySquare) {
12724         DisplayMoveError(_("That square is occupied"));
12725         return;
12726     }
12727
12728     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12729 }
12730
12731 void
12732 AcceptEvent()
12733 {
12734     /* Accept a pending offer of any kind from opponent */
12735
12736     if (appData.icsActive) {
12737         SendToICS(ics_prefix);
12738         SendToICS("accept\n");
12739     } else if (cmailMsgLoaded) {
12740         if (currentMove == cmailOldMove &&
12741             commentList[cmailOldMove] != NULL &&
12742             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12743                    "Black offers a draw" : "White offers a draw")) {
12744             TruncateGame();
12745             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12746             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12747         } else {
12748             DisplayError(_("There is no pending offer on this move"), 0);
12749             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12750         }
12751     } else {
12752         /* Not used for offers from chess program */
12753     }
12754 }
12755
12756 void
12757 DeclineEvent()
12758 {
12759     /* Decline a pending offer of any kind from opponent */
12760
12761     if (appData.icsActive) {
12762         SendToICS(ics_prefix);
12763         SendToICS("decline\n");
12764     } else if (cmailMsgLoaded) {
12765         if (currentMove == cmailOldMove &&
12766             commentList[cmailOldMove] != NULL &&
12767             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12768                    "Black offers a draw" : "White offers a draw")) {
12769 #ifdef NOTDEF
12770             AppendComment(cmailOldMove, "Draw declined", TRUE);
12771             DisplayComment(cmailOldMove - 1, "Draw declined");
12772 #endif /*NOTDEF*/
12773         } else {
12774             DisplayError(_("There is no pending offer on this move"), 0);
12775         }
12776     } else {
12777         /* Not used for offers from chess program */
12778     }
12779 }
12780
12781 void
12782 RematchEvent()
12783 {
12784     /* Issue ICS rematch command */
12785     if (appData.icsActive) {
12786         SendToICS(ics_prefix);
12787         SendToICS("rematch\n");
12788     }
12789 }
12790
12791 void
12792 CallFlagEvent()
12793 {
12794     /* Call your opponent's flag (claim a win on time) */
12795     if (appData.icsActive) {
12796         SendToICS(ics_prefix);
12797         SendToICS("flag\n");
12798     } else {
12799         switch (gameMode) {
12800           default:
12801             return;
12802           case MachinePlaysWhite:
12803             if (whiteFlag) {
12804                 if (blackFlag)
12805                   GameEnds(GameIsDrawn, "Both players ran out of time",
12806                            GE_PLAYER);
12807                 else
12808                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12809             } else {
12810                 DisplayError(_("Your opponent is not out of time"), 0);
12811             }
12812             break;
12813           case MachinePlaysBlack:
12814             if (blackFlag) {
12815                 if (whiteFlag)
12816                   GameEnds(GameIsDrawn, "Both players ran out of time",
12817                            GE_PLAYER);
12818                 else
12819                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12820             } else {
12821                 DisplayError(_("Your opponent is not out of time"), 0);
12822             }
12823             break;
12824         }
12825     }
12826 }
12827
12828 void
12829 ClockClick(int which)
12830 {       // [HGM] code moved to back-end from winboard.c
12831         if(which) { // black clock
12832           if (gameMode == EditPosition || gameMode == IcsExamining) {
12833             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12834             SetBlackToPlayEvent();
12835           } else if (gameMode == EditGame || shiftKey) {
12836             AdjustClock(which, -1);
12837           } else if (gameMode == IcsPlayingWhite ||
12838                      gameMode == MachinePlaysBlack) {
12839             CallFlagEvent();
12840           }
12841         } else { // white clock
12842           if (gameMode == EditPosition || gameMode == IcsExamining) {
12843             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12844             SetWhiteToPlayEvent();
12845           } else if (gameMode == EditGame || shiftKey) {
12846             AdjustClock(which, -1);
12847           } else if (gameMode == IcsPlayingBlack ||
12848                    gameMode == MachinePlaysWhite) {
12849             CallFlagEvent();
12850           }
12851         }
12852 }
12853
12854 void
12855 DrawEvent()
12856 {
12857     /* Offer draw or accept pending draw offer from opponent */
12858
12859     if (appData.icsActive) {
12860         /* Note: tournament rules require draw offers to be
12861            made after you make your move but before you punch
12862            your clock.  Currently ICS doesn't let you do that;
12863            instead, you immediately punch your clock after making
12864            a move, but you can offer a draw at any time. */
12865
12866         SendToICS(ics_prefix);
12867         SendToICS("draw\n");
12868         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12869     } else if (cmailMsgLoaded) {
12870         if (currentMove == cmailOldMove &&
12871             commentList[cmailOldMove] != NULL &&
12872             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12873                    "Black offers a draw" : "White offers a draw")) {
12874             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12875             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12876         } else if (currentMove == cmailOldMove + 1) {
12877             char *offer = WhiteOnMove(cmailOldMove) ?
12878               "White offers a draw" : "Black offers a draw";
12879             AppendComment(currentMove, offer, TRUE);
12880             DisplayComment(currentMove - 1, offer);
12881             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12882         } else {
12883             DisplayError(_("You must make your move before offering a draw"), 0);
12884             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12885         }
12886     } else if (first.offeredDraw) {
12887         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12888     } else {
12889         if (first.sendDrawOffers) {
12890             SendToProgram("draw\n", &first);
12891             userOfferedDraw = TRUE;
12892         }
12893     }
12894 }
12895
12896 void
12897 AdjournEvent()
12898 {
12899     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12900
12901     if (appData.icsActive) {
12902         SendToICS(ics_prefix);
12903         SendToICS("adjourn\n");
12904     } else {
12905         /* Currently GNU Chess doesn't offer or accept Adjourns */
12906     }
12907 }
12908
12909
12910 void
12911 AbortEvent()
12912 {
12913     /* Offer Abort or accept pending Abort offer from opponent */
12914
12915     if (appData.icsActive) {
12916         SendToICS(ics_prefix);
12917         SendToICS("abort\n");
12918     } else {
12919         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12920     }
12921 }
12922
12923 void
12924 ResignEvent()
12925 {
12926     /* Resign.  You can do this even if it's not your turn. */
12927
12928     if (appData.icsActive) {
12929         SendToICS(ics_prefix);
12930         SendToICS("resign\n");
12931     } else {
12932         switch (gameMode) {
12933           case MachinePlaysWhite:
12934             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12935             break;
12936           case MachinePlaysBlack:
12937             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12938             break;
12939           case EditGame:
12940             if (cmailMsgLoaded) {
12941                 TruncateGame();
12942                 if (WhiteOnMove(cmailOldMove)) {
12943                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12944                 } else {
12945                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12946                 }
12947                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12948             }
12949             break;
12950           default:
12951             break;
12952         }
12953     }
12954 }
12955
12956
12957 void
12958 StopObservingEvent()
12959 {
12960     /* Stop observing current games */
12961     SendToICS(ics_prefix);
12962     SendToICS("unobserve\n");
12963 }
12964
12965 void
12966 StopExaminingEvent()
12967 {
12968     /* Stop observing current game */
12969     SendToICS(ics_prefix);
12970     SendToICS("unexamine\n");
12971 }
12972
12973 void
12974 ForwardInner(target)
12975      int target;
12976 {
12977     int limit;
12978
12979     if (appData.debugMode)
12980         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12981                 target, currentMove, forwardMostMove);
12982
12983     if (gameMode == EditPosition)
12984       return;
12985
12986     if (gameMode == PlayFromGameFile && !pausing)
12987       PauseEvent();
12988
12989     if (gameMode == IcsExamining && pausing)
12990       limit = pauseExamForwardMostMove;
12991     else
12992       limit = forwardMostMove;
12993
12994     if (target > limit) target = limit;
12995
12996     if (target > 0 && moveList[target - 1][0]) {
12997         int fromX, fromY, toX, toY;
12998         toX = moveList[target - 1][2] - AAA;
12999         toY = moveList[target - 1][3] - ONE;
13000         if (moveList[target - 1][1] == '@') {
13001             if (appData.highlightLastMove) {
13002                 SetHighlights(-1, -1, toX, toY);
13003             }
13004         } else {
13005             fromX = moveList[target - 1][0] - AAA;
13006             fromY = moveList[target - 1][1] - ONE;
13007             if (target == currentMove + 1) {
13008                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13009             }
13010             if (appData.highlightLastMove) {
13011                 SetHighlights(fromX, fromY, toX, toY);
13012             }
13013         }
13014     }
13015     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13016         gameMode == Training || gameMode == PlayFromGameFile ||
13017         gameMode == AnalyzeFile) {
13018         while (currentMove < target) {
13019             SendMoveToProgram(currentMove++, &first);
13020         }
13021     } else {
13022         currentMove = target;
13023     }
13024
13025     if (gameMode == EditGame || gameMode == EndOfGame) {
13026         whiteTimeRemaining = timeRemaining[0][currentMove];
13027         blackTimeRemaining = timeRemaining[1][currentMove];
13028     }
13029     DisplayBothClocks();
13030     DisplayMove(currentMove - 1);
13031     DrawPosition(FALSE, boards[currentMove]);
13032     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13033     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13034         DisplayComment(currentMove - 1, commentList[currentMove]);
13035     }
13036 }
13037
13038
13039 void
13040 ForwardEvent()
13041 {
13042     if (gameMode == IcsExamining && !pausing) {
13043         SendToICS(ics_prefix);
13044         SendToICS("forward\n");
13045     } else {
13046         ForwardInner(currentMove + 1);
13047     }
13048 }
13049
13050 void
13051 ToEndEvent()
13052 {
13053     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13054         /* to optimze, we temporarily turn off analysis mode while we feed
13055          * the remaining moves to the engine. Otherwise we get analysis output
13056          * after each move.
13057          */
13058         if (first.analysisSupport) {
13059           SendToProgram("exit\nforce\n", &first);
13060           first.analyzing = FALSE;
13061         }
13062     }
13063
13064     if (gameMode == IcsExamining && !pausing) {
13065         SendToICS(ics_prefix);
13066         SendToICS("forward 999999\n");
13067     } else {
13068         ForwardInner(forwardMostMove);
13069     }
13070
13071     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13072         /* we have fed all the moves, so reactivate analysis mode */
13073         SendToProgram("analyze\n", &first);
13074         first.analyzing = TRUE;
13075         /*first.maybeThinking = TRUE;*/
13076         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13077     }
13078 }
13079
13080 void
13081 BackwardInner(target)
13082      int target;
13083 {
13084     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13085
13086     if (appData.debugMode)
13087         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13088                 target, currentMove, forwardMostMove);
13089
13090     if (gameMode == EditPosition) return;
13091     if (currentMove <= backwardMostMove) {
13092         ClearHighlights();
13093         DrawPosition(full_redraw, boards[currentMove]);
13094         return;
13095     }
13096     if (gameMode == PlayFromGameFile && !pausing)
13097       PauseEvent();
13098
13099     if (moveList[target][0]) {
13100         int fromX, fromY, toX, toY;
13101         toX = moveList[target][2] - AAA;
13102         toY = moveList[target][3] - ONE;
13103         if (moveList[target][1] == '@') {
13104             if (appData.highlightLastMove) {
13105                 SetHighlights(-1, -1, toX, toY);
13106             }
13107         } else {
13108             fromX = moveList[target][0] - AAA;
13109             fromY = moveList[target][1] - ONE;
13110             if (target == currentMove - 1) {
13111                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13112             }
13113             if (appData.highlightLastMove) {
13114                 SetHighlights(fromX, fromY, toX, toY);
13115             }
13116         }
13117     }
13118     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13119         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13120         while (currentMove > target) {
13121             SendToProgram("undo\n", &first);
13122             currentMove--;
13123         }
13124     } else {
13125         currentMove = target;
13126     }
13127
13128     if (gameMode == EditGame || gameMode == EndOfGame) {
13129         whiteTimeRemaining = timeRemaining[0][currentMove];
13130         blackTimeRemaining = timeRemaining[1][currentMove];
13131     }
13132     DisplayBothClocks();
13133     DisplayMove(currentMove - 1);
13134     DrawPosition(full_redraw, boards[currentMove]);
13135     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13136     // [HGM] PV info: routine tests if comment empty
13137     DisplayComment(currentMove - 1, commentList[currentMove]);
13138 }
13139
13140 void
13141 BackwardEvent()
13142 {
13143     if (gameMode == IcsExamining && !pausing) {
13144         SendToICS(ics_prefix);
13145         SendToICS("backward\n");
13146     } else {
13147         BackwardInner(currentMove - 1);
13148     }
13149 }
13150
13151 void
13152 ToStartEvent()
13153 {
13154     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13155         /* to optimize, we temporarily turn off analysis mode while we undo
13156          * all the moves. Otherwise we get analysis output after each undo.
13157          */
13158         if (first.analysisSupport) {
13159           SendToProgram("exit\nforce\n", &first);
13160           first.analyzing = FALSE;
13161         }
13162     }
13163
13164     if (gameMode == IcsExamining && !pausing) {
13165         SendToICS(ics_prefix);
13166         SendToICS("backward 999999\n");
13167     } else {
13168         BackwardInner(backwardMostMove);
13169     }
13170
13171     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13172         /* we have fed all the moves, so reactivate analysis mode */
13173         SendToProgram("analyze\n", &first);
13174         first.analyzing = TRUE;
13175         /*first.maybeThinking = TRUE;*/
13176         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13177     }
13178 }
13179
13180 void
13181 ToNrEvent(int to)
13182 {
13183   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13184   if (to >= forwardMostMove) to = forwardMostMove;
13185   if (to <= backwardMostMove) to = backwardMostMove;
13186   if (to < currentMove) {
13187     BackwardInner(to);
13188   } else {
13189     ForwardInner(to);
13190   }
13191 }
13192
13193 void
13194 RevertEvent(Boolean annotate)
13195 {
13196     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13197         return;
13198     }
13199     if (gameMode != IcsExamining) {
13200         DisplayError(_("You are not examining a game"), 0);
13201         return;
13202     }
13203     if (pausing) {
13204         DisplayError(_("You can't revert while pausing"), 0);
13205         return;
13206     }
13207     SendToICS(ics_prefix);
13208     SendToICS("revert\n");
13209 }
13210
13211 void
13212 RetractMoveEvent()
13213 {
13214     switch (gameMode) {
13215       case MachinePlaysWhite:
13216       case MachinePlaysBlack:
13217         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13218             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13219             return;
13220         }
13221         if (forwardMostMove < 2) return;
13222         currentMove = forwardMostMove = forwardMostMove - 2;
13223         whiteTimeRemaining = timeRemaining[0][currentMove];
13224         blackTimeRemaining = timeRemaining[1][currentMove];
13225         DisplayBothClocks();
13226         DisplayMove(currentMove - 1);
13227         ClearHighlights();/*!! could figure this out*/
13228         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13229         SendToProgram("remove\n", &first);
13230         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13231         break;
13232
13233       case BeginningOfGame:
13234       default:
13235         break;
13236
13237       case IcsPlayingWhite:
13238       case IcsPlayingBlack:
13239         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13240             SendToICS(ics_prefix);
13241             SendToICS("takeback 2\n");
13242         } else {
13243             SendToICS(ics_prefix);
13244             SendToICS("takeback 1\n");
13245         }
13246         break;
13247     }
13248 }
13249
13250 void
13251 MoveNowEvent()
13252 {
13253     ChessProgramState *cps;
13254
13255     switch (gameMode) {
13256       case MachinePlaysWhite:
13257         if (!WhiteOnMove(forwardMostMove)) {
13258             DisplayError(_("It is your turn"), 0);
13259             return;
13260         }
13261         cps = &first;
13262         break;
13263       case MachinePlaysBlack:
13264         if (WhiteOnMove(forwardMostMove)) {
13265             DisplayError(_("It is your turn"), 0);
13266             return;
13267         }
13268         cps = &first;
13269         break;
13270       case TwoMachinesPlay:
13271         if (WhiteOnMove(forwardMostMove) ==
13272             (first.twoMachinesColor[0] == 'w')) {
13273             cps = &first;
13274         } else {
13275             cps = &second;
13276         }
13277         break;
13278       case BeginningOfGame:
13279       default:
13280         return;
13281     }
13282     SendToProgram("?\n", cps);
13283 }
13284
13285 void
13286 TruncateGameEvent()
13287 {
13288     EditGameEvent();
13289     if (gameMode != EditGame) return;
13290     TruncateGame();
13291 }
13292
13293 void
13294 TruncateGame()
13295 {
13296     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13297     if (forwardMostMove > currentMove) {
13298         if (gameInfo.resultDetails != NULL) {
13299             free(gameInfo.resultDetails);
13300             gameInfo.resultDetails = NULL;
13301             gameInfo.result = GameUnfinished;
13302         }
13303         forwardMostMove = currentMove;
13304         HistorySet(parseList, backwardMostMove, forwardMostMove,
13305                    currentMove-1);
13306     }
13307 }
13308
13309 void
13310 HintEvent()
13311 {
13312     if (appData.noChessProgram) return;
13313     switch (gameMode) {
13314       case MachinePlaysWhite:
13315         if (WhiteOnMove(forwardMostMove)) {
13316             DisplayError(_("Wait until your turn"), 0);
13317             return;
13318         }
13319         break;
13320       case BeginningOfGame:
13321       case MachinePlaysBlack:
13322         if (!WhiteOnMove(forwardMostMove)) {
13323             DisplayError(_("Wait until your turn"), 0);
13324             return;
13325         }
13326         break;
13327       default:
13328         DisplayError(_("No hint available"), 0);
13329         return;
13330     }
13331     SendToProgram("hint\n", &first);
13332     hintRequested = TRUE;
13333 }
13334
13335 void
13336 BookEvent()
13337 {
13338     if (appData.noChessProgram) return;
13339     switch (gameMode) {
13340       case MachinePlaysWhite:
13341         if (WhiteOnMove(forwardMostMove)) {
13342             DisplayError(_("Wait until your turn"), 0);
13343             return;
13344         }
13345         break;
13346       case BeginningOfGame:
13347       case MachinePlaysBlack:
13348         if (!WhiteOnMove(forwardMostMove)) {
13349             DisplayError(_("Wait until your turn"), 0);
13350             return;
13351         }
13352         break;
13353       case EditPosition:
13354         EditPositionDone(TRUE);
13355         break;
13356       case TwoMachinesPlay:
13357         return;
13358       default:
13359         break;
13360     }
13361     SendToProgram("bk\n", &first);
13362     bookOutput[0] = NULLCHAR;
13363     bookRequested = TRUE;
13364 }
13365
13366 void
13367 AboutGameEvent()
13368 {
13369     char *tags = PGNTags(&gameInfo);
13370     TagsPopUp(tags, CmailMsg());
13371     free(tags);
13372 }
13373
13374 /* end button procedures */
13375
13376 void
13377 PrintPosition(fp, move)
13378      FILE *fp;
13379      int move;
13380 {
13381     int i, j;
13382
13383     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13384         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13385             char c = PieceToChar(boards[move][i][j]);
13386             fputc(c == 'x' ? '.' : c, fp);
13387             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13388         }
13389     }
13390     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13391       fprintf(fp, "white to play\n");
13392     else
13393       fprintf(fp, "black to play\n");
13394 }
13395
13396 void
13397 PrintOpponents(fp)
13398      FILE *fp;
13399 {
13400     if (gameInfo.white != NULL) {
13401         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13402     } else {
13403         fprintf(fp, "\n");
13404     }
13405 }
13406
13407 /* Find last component of program's own name, using some heuristics */
13408 void
13409 TidyProgramName(prog, host, buf)
13410      char *prog, *host, buf[MSG_SIZ];
13411 {
13412     char *p, *q;
13413     int local = (strcmp(host, "localhost") == 0);
13414     while (!local && (p = strchr(prog, ';')) != NULL) {
13415         p++;
13416         while (*p == ' ') p++;
13417         prog = p;
13418     }
13419     if (*prog == '"' || *prog == '\'') {
13420         q = strchr(prog + 1, *prog);
13421     } else {
13422         q = strchr(prog, ' ');
13423     }
13424     if (q == NULL) q = prog + strlen(prog);
13425     p = q;
13426     while (p >= prog && *p != '/' && *p != '\\') p--;
13427     p++;
13428     if(p == prog && *p == '"') p++;
13429     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13430     memcpy(buf, p, q - p);
13431     buf[q - p] = NULLCHAR;
13432     if (!local) {
13433         strcat(buf, "@");
13434         strcat(buf, host);
13435     }
13436 }
13437
13438 char *
13439 TimeControlTagValue()
13440 {
13441     char buf[MSG_SIZ];
13442     if (!appData.clockMode) {
13443       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13444     } else if (movesPerSession > 0) {
13445       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13446     } else if (timeIncrement == 0) {
13447       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13448     } else {
13449       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13450     }
13451     return StrSave(buf);
13452 }
13453
13454 void
13455 SetGameInfo()
13456 {
13457     /* This routine is used only for certain modes */
13458     VariantClass v = gameInfo.variant;
13459     ChessMove r = GameUnfinished;
13460     char *p = NULL;
13461
13462     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13463         r = gameInfo.result;
13464         p = gameInfo.resultDetails;
13465         gameInfo.resultDetails = NULL;
13466     }
13467     ClearGameInfo(&gameInfo);
13468     gameInfo.variant = v;
13469
13470     switch (gameMode) {
13471       case MachinePlaysWhite:
13472         gameInfo.event = StrSave( appData.pgnEventHeader );
13473         gameInfo.site = StrSave(HostName());
13474         gameInfo.date = PGNDate();
13475         gameInfo.round = StrSave("-");
13476         gameInfo.white = StrSave(first.tidy);
13477         gameInfo.black = StrSave(UserName());
13478         gameInfo.timeControl = TimeControlTagValue();
13479         break;
13480
13481       case MachinePlaysBlack:
13482         gameInfo.event = StrSave( appData.pgnEventHeader );
13483         gameInfo.site = StrSave(HostName());
13484         gameInfo.date = PGNDate();
13485         gameInfo.round = StrSave("-");
13486         gameInfo.white = StrSave(UserName());
13487         gameInfo.black = StrSave(first.tidy);
13488         gameInfo.timeControl = TimeControlTagValue();
13489         break;
13490
13491       case TwoMachinesPlay:
13492         gameInfo.event = StrSave( appData.pgnEventHeader );
13493         gameInfo.site = StrSave(HostName());
13494         gameInfo.date = PGNDate();
13495         if (matchGame > 0) {
13496             char buf[MSG_SIZ];
13497             snprintf(buf, MSG_SIZ, "%d", matchGame);
13498             gameInfo.round = StrSave(buf);
13499         } else {
13500             gameInfo.round = StrSave("-");
13501         }
13502         if (first.twoMachinesColor[0] == 'w') {
13503             gameInfo.white = StrSave(first.tidy);
13504             gameInfo.black = StrSave(second.tidy);
13505         } else {
13506             gameInfo.white = StrSave(second.tidy);
13507             gameInfo.black = StrSave(first.tidy);
13508         }
13509         gameInfo.timeControl = TimeControlTagValue();
13510         break;
13511
13512       case EditGame:
13513         gameInfo.event = StrSave("Edited game");
13514         gameInfo.site = StrSave(HostName());
13515         gameInfo.date = PGNDate();
13516         gameInfo.round = StrSave("-");
13517         gameInfo.white = StrSave("-");
13518         gameInfo.black = StrSave("-");
13519         gameInfo.result = r;
13520         gameInfo.resultDetails = p;
13521         break;
13522
13523       case EditPosition:
13524         gameInfo.event = StrSave("Edited position");
13525         gameInfo.site = StrSave(HostName());
13526         gameInfo.date = PGNDate();
13527         gameInfo.round = StrSave("-");
13528         gameInfo.white = StrSave("-");
13529         gameInfo.black = StrSave("-");
13530         break;
13531
13532       case IcsPlayingWhite:
13533       case IcsPlayingBlack:
13534       case IcsObserving:
13535       case IcsExamining:
13536         break;
13537
13538       case PlayFromGameFile:
13539         gameInfo.event = StrSave("Game from non-PGN file");
13540         gameInfo.site = StrSave(HostName());
13541         gameInfo.date = PGNDate();
13542         gameInfo.round = StrSave("-");
13543         gameInfo.white = StrSave("?");
13544         gameInfo.black = StrSave("?");
13545         break;
13546
13547       default:
13548         break;
13549     }
13550 }
13551
13552 void
13553 ReplaceComment(index, text)
13554      int index;
13555      char *text;
13556 {
13557     int len;
13558     char *p;
13559     float score;
13560
13561     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13562        pvInfoList[index-1].depth == len &&
13563        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13564        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13565     while (*text == '\n') text++;
13566     len = strlen(text);
13567     while (len > 0 && text[len - 1] == '\n') len--;
13568
13569     if (commentList[index] != NULL)
13570       free(commentList[index]);
13571
13572     if (len == 0) {
13573         commentList[index] = NULL;
13574         return;
13575     }
13576   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13577       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13578       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13579     commentList[index] = (char *) malloc(len + 2);
13580     strncpy(commentList[index], text, len);
13581     commentList[index][len] = '\n';
13582     commentList[index][len + 1] = NULLCHAR;
13583   } else {
13584     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13585     char *p;
13586     commentList[index] = (char *) malloc(len + 7);
13587     safeStrCpy(commentList[index], "{\n", 3);
13588     safeStrCpy(commentList[index]+2, text, len+1);
13589     commentList[index][len+2] = NULLCHAR;
13590     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13591     strcat(commentList[index], "\n}\n");
13592   }
13593 }
13594
13595 void
13596 CrushCRs(text)
13597      char *text;
13598 {
13599   char *p = text;
13600   char *q = text;
13601   char ch;
13602
13603   do {
13604     ch = *p++;
13605     if (ch == '\r') continue;
13606     *q++ = ch;
13607   } while (ch != '\0');
13608 }
13609
13610 void
13611 AppendComment(index, text, addBraces)
13612      int index;
13613      char *text;
13614      Boolean addBraces; // [HGM] braces: tells if we should add {}
13615 {
13616     int oldlen, len;
13617     char *old;
13618
13619 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13620     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13621
13622     CrushCRs(text);
13623     while (*text == '\n') text++;
13624     len = strlen(text);
13625     while (len > 0 && text[len - 1] == '\n') len--;
13626
13627     if (len == 0) return;
13628
13629     if (commentList[index] != NULL) {
13630         old = commentList[index];
13631         oldlen = strlen(old);
13632         while(commentList[index][oldlen-1] ==  '\n')
13633           commentList[index][--oldlen] = NULLCHAR;
13634         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13635         safeStrCpy(commentList[index], old, oldlen + len + 6);
13636         free(old);
13637         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13638         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13639           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13640           while (*text == '\n') { text++; len--; }
13641           commentList[index][--oldlen] = NULLCHAR;
13642       }
13643         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13644         else          strcat(commentList[index], "\n");
13645         strcat(commentList[index], text);
13646         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13647         else          strcat(commentList[index], "\n");
13648     } else {
13649         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13650         if(addBraces)
13651           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13652         else commentList[index][0] = NULLCHAR;
13653         strcat(commentList[index], text);
13654         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13655         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13656     }
13657 }
13658
13659 static char * FindStr( char * text, char * sub_text )
13660 {
13661     char * result = strstr( text, sub_text );
13662
13663     if( result != NULL ) {
13664         result += strlen( sub_text );
13665     }
13666
13667     return result;
13668 }
13669
13670 /* [AS] Try to extract PV info from PGN comment */
13671 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13672 char *GetInfoFromComment( int index, char * text )
13673 {
13674     char * sep = text, *p;
13675
13676     if( text != NULL && index > 0 ) {
13677         int score = 0;
13678         int depth = 0;
13679         int time = -1, sec = 0, deci;
13680         char * s_eval = FindStr( text, "[%eval " );
13681         char * s_emt = FindStr( text, "[%emt " );
13682
13683         if( s_eval != NULL || s_emt != NULL ) {
13684             /* New style */
13685             char delim;
13686
13687             if( s_eval != NULL ) {
13688                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13689                     return text;
13690                 }
13691
13692                 if( delim != ']' ) {
13693                     return text;
13694                 }
13695             }
13696
13697             if( s_emt != NULL ) {
13698             }
13699                 return text;
13700         }
13701         else {
13702             /* We expect something like: [+|-]nnn.nn/dd */
13703             int score_lo = 0;
13704
13705             if(*text != '{') return text; // [HGM] braces: must be normal comment
13706
13707             sep = strchr( text, '/' );
13708             if( sep == NULL || sep < (text+4) ) {
13709                 return text;
13710             }
13711
13712             p = text;
13713             if(p[1] == '(') { // comment starts with PV
13714                p = strchr(p, ')'); // locate end of PV
13715                if(p == NULL || sep < p+5) return text;
13716                // at this point we have something like "{(.*) +0.23/6 ..."
13717                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13718                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13719                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13720             }
13721             time = -1; sec = -1; deci = -1;
13722             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13723                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13724                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13725                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13726                 return text;
13727             }
13728
13729             if( score_lo < 0 || score_lo >= 100 ) {
13730                 return text;
13731             }
13732
13733             if(sec >= 0) time = 600*time + 10*sec; else
13734             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13735
13736             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13737
13738             /* [HGM] PV time: now locate end of PV info */
13739             while( *++sep >= '0' && *sep <= '9'); // strip depth
13740             if(time >= 0)
13741             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13742             if(sec >= 0)
13743             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13744             if(deci >= 0)
13745             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13746             while(*sep == ' ') sep++;
13747         }
13748
13749         if( depth <= 0 ) {
13750             return text;
13751         }
13752
13753         if( time < 0 ) {
13754             time = -1;
13755         }
13756
13757         pvInfoList[index-1].depth = depth;
13758         pvInfoList[index-1].score = score;
13759         pvInfoList[index-1].time  = 10*time; // centi-sec
13760         if(*sep == '}') *sep = 0; else *--sep = '{';
13761         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13762     }
13763     return sep;
13764 }
13765
13766 void
13767 SendToProgram(message, cps)
13768      char *message;
13769      ChessProgramState *cps;
13770 {
13771     int count, outCount, error;
13772     char buf[MSG_SIZ];
13773
13774     if (cps->pr == NULL) return;
13775     Attention(cps);
13776
13777     if (appData.debugMode) {
13778         TimeMark now;
13779         GetTimeMark(&now);
13780         fprintf(debugFP, "%ld >%-6s: %s",
13781                 SubtractTimeMarks(&now, &programStartTime),
13782                 cps->which, message);
13783     }
13784
13785     count = strlen(message);
13786     outCount = OutputToProcess(cps->pr, message, count, &error);
13787     if (outCount < count && !exiting
13788                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13789       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13790         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13791             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13792                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13793                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13794             } else {
13795                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13796             }
13797             gameInfo.resultDetails = StrSave(buf);
13798         }
13799         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13800     }
13801 }
13802
13803 void
13804 ReceiveFromProgram(isr, closure, message, count, error)
13805      InputSourceRef isr;
13806      VOIDSTAR closure;
13807      char *message;
13808      int count;
13809      int error;
13810 {
13811     char *end_str;
13812     char buf[MSG_SIZ];
13813     ChessProgramState *cps = (ChessProgramState *)closure;
13814
13815     if (isr != cps->isr) return; /* Killed intentionally */
13816     if (count <= 0) {
13817         if (count == 0) {
13818             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13819                     _(cps->which), cps->program);
13820         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13821                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13822                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13823                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13824                 } else {
13825                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13826                 }
13827                 gameInfo.resultDetails = StrSave(buf);
13828             }
13829             RemoveInputSource(cps->isr);
13830             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13831         } else {
13832             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13833                     _(cps->which), cps->program);
13834             RemoveInputSource(cps->isr);
13835
13836             /* [AS] Program is misbehaving badly... kill it */
13837             if( count == -2 ) {
13838                 DestroyChildProcess( cps->pr, 9 );
13839                 cps->pr = NoProc;
13840             }
13841
13842             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13843         }
13844         return;
13845     }
13846
13847     if ((end_str = strchr(message, '\r')) != NULL)
13848       *end_str = NULLCHAR;
13849     if ((end_str = strchr(message, '\n')) != NULL)
13850       *end_str = NULLCHAR;
13851
13852     if (appData.debugMode) {
13853         TimeMark now; int print = 1;
13854         char *quote = ""; char c; int i;
13855
13856         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13857                 char start = message[0];
13858                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13859                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13860                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13861                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13862                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13863                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13864                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13865                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13866                    sscanf(message, "hint: %c", &c)!=1 && 
13867                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13868                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13869                     print = (appData.engineComments >= 2);
13870                 }
13871                 message[0] = start; // restore original message
13872         }
13873         if(print) {
13874                 GetTimeMark(&now);
13875                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13876                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13877                         quote,
13878                         message);
13879         }
13880     }
13881
13882     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13883     if (appData.icsEngineAnalyze) {
13884         if (strstr(message, "whisper") != NULL ||
13885              strstr(message, "kibitz") != NULL ||
13886             strstr(message, "tellics") != NULL) return;
13887     }
13888
13889     HandleMachineMove(message, cps);
13890 }
13891
13892
13893 void
13894 SendTimeControl(cps, mps, tc, inc, sd, st)
13895      ChessProgramState *cps;
13896      int mps, inc, sd, st;
13897      long tc;
13898 {
13899     char buf[MSG_SIZ];
13900     int seconds;
13901
13902     if( timeControl_2 > 0 ) {
13903         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13904             tc = timeControl_2;
13905         }
13906     }
13907     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13908     inc /= cps->timeOdds;
13909     st  /= cps->timeOdds;
13910
13911     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13912
13913     if (st > 0) {
13914       /* Set exact time per move, normally using st command */
13915       if (cps->stKludge) {
13916         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13917         seconds = st % 60;
13918         if (seconds == 0) {
13919           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13920         } else {
13921           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13922         }
13923       } else {
13924         snprintf(buf, MSG_SIZ, "st %d\n", st);
13925       }
13926     } else {
13927       /* Set conventional or incremental time control, using level command */
13928       if (seconds == 0) {
13929         /* Note old gnuchess bug -- minutes:seconds used to not work.
13930            Fixed in later versions, but still avoid :seconds
13931            when seconds is 0. */
13932         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13933       } else {
13934         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13935                  seconds, inc/1000.);
13936       }
13937     }
13938     SendToProgram(buf, cps);
13939
13940     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13941     /* Orthogonally, limit search to given depth */
13942     if (sd > 0) {
13943       if (cps->sdKludge) {
13944         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13945       } else {
13946         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13947       }
13948       SendToProgram(buf, cps);
13949     }
13950
13951     if(cps->nps >= 0) { /* [HGM] nps */
13952         if(cps->supportsNPS == FALSE)
13953           cps->nps = -1; // don't use if engine explicitly says not supported!
13954         else {
13955           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13956           SendToProgram(buf, cps);
13957         }
13958     }
13959 }
13960
13961 ChessProgramState *WhitePlayer()
13962 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13963 {
13964     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13965        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13966         return &second;
13967     return &first;
13968 }
13969
13970 void
13971 SendTimeRemaining(cps, machineWhite)
13972      ChessProgramState *cps;
13973      int /*boolean*/ machineWhite;
13974 {
13975     char message[MSG_SIZ];
13976     long time, otime;
13977
13978     /* Note: this routine must be called when the clocks are stopped
13979        or when they have *just* been set or switched; otherwise
13980        it will be off by the time since the current tick started.
13981     */
13982     if (machineWhite) {
13983         time = whiteTimeRemaining / 10;
13984         otime = blackTimeRemaining / 10;
13985     } else {
13986         time = blackTimeRemaining / 10;
13987         otime = whiteTimeRemaining / 10;
13988     }
13989     /* [HGM] translate opponent's time by time-odds factor */
13990     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13991     if (appData.debugMode) {
13992         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13993     }
13994
13995     if (time <= 0) time = 1;
13996     if (otime <= 0) otime = 1;
13997
13998     snprintf(message, MSG_SIZ, "time %ld\n", time);
13999     SendToProgram(message, cps);
14000
14001     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14002     SendToProgram(message, cps);
14003 }
14004
14005 int
14006 BoolFeature(p, name, loc, cps)
14007      char **p;
14008      char *name;
14009      int *loc;
14010      ChessProgramState *cps;
14011 {
14012   char buf[MSG_SIZ];
14013   int len = strlen(name);
14014   int val;
14015
14016   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14017     (*p) += len + 1;
14018     sscanf(*p, "%d", &val);
14019     *loc = (val != 0);
14020     while (**p && **p != ' ')
14021       (*p)++;
14022     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14023     SendToProgram(buf, cps);
14024     return TRUE;
14025   }
14026   return FALSE;
14027 }
14028
14029 int
14030 IntFeature(p, name, loc, cps)
14031      char **p;
14032      char *name;
14033      int *loc;
14034      ChessProgramState *cps;
14035 {
14036   char buf[MSG_SIZ];
14037   int len = strlen(name);
14038   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14039     (*p) += len + 1;
14040     sscanf(*p, "%d", loc);
14041     while (**p && **p != ' ') (*p)++;
14042     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14043     SendToProgram(buf, cps);
14044     return TRUE;
14045   }
14046   return FALSE;
14047 }
14048
14049 int
14050 StringFeature(p, name, loc, cps)
14051      char **p;
14052      char *name;
14053      char loc[];
14054      ChessProgramState *cps;
14055 {
14056   char buf[MSG_SIZ];
14057   int len = strlen(name);
14058   if (strncmp((*p), name, len) == 0
14059       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14060     (*p) += len + 2;
14061     sscanf(*p, "%[^\"]", loc);
14062     while (**p && **p != '\"') (*p)++;
14063     if (**p == '\"') (*p)++;
14064     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14065     SendToProgram(buf, cps);
14066     return TRUE;
14067   }
14068   return FALSE;
14069 }
14070
14071 int
14072 ParseOption(Option *opt, ChessProgramState *cps)
14073 // [HGM] options: process the string that defines an engine option, and determine
14074 // name, type, default value, and allowed value range
14075 {
14076         char *p, *q, buf[MSG_SIZ];
14077         int n, min = (-1)<<31, max = 1<<31, def;
14078
14079         if(p = strstr(opt->name, " -spin ")) {
14080             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14081             if(max < min) max = min; // enforce consistency
14082             if(def < min) def = min;
14083             if(def > max) def = max;
14084             opt->value = def;
14085             opt->min = min;
14086             opt->max = max;
14087             opt->type = Spin;
14088         } else if((p = strstr(opt->name, " -slider "))) {
14089             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14090             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14091             if(max < min) max = min; // enforce consistency
14092             if(def < min) def = min;
14093             if(def > max) def = max;
14094             opt->value = def;
14095             opt->min = min;
14096             opt->max = max;
14097             opt->type = Spin; // Slider;
14098         } else if((p = strstr(opt->name, " -string "))) {
14099             opt->textValue = p+9;
14100             opt->type = TextBox;
14101         } else if((p = strstr(opt->name, " -file "))) {
14102             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14103             opt->textValue = p+7;
14104             opt->type = FileName; // FileName;
14105         } else if((p = strstr(opt->name, " -path "))) {
14106             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14107             opt->textValue = p+7;
14108             opt->type = PathName; // PathName;
14109         } else if(p = strstr(opt->name, " -check ")) {
14110             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14111             opt->value = (def != 0);
14112             opt->type = CheckBox;
14113         } else if(p = strstr(opt->name, " -combo ")) {
14114             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14115             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14116             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14117             opt->value = n = 0;
14118             while(q = StrStr(q, " /// ")) {
14119                 n++; *q = 0;    // count choices, and null-terminate each of them
14120                 q += 5;
14121                 if(*q == '*') { // remember default, which is marked with * prefix
14122                     q++;
14123                     opt->value = n;
14124                 }
14125                 cps->comboList[cps->comboCnt++] = q;
14126             }
14127             cps->comboList[cps->comboCnt++] = NULL;
14128             opt->max = n + 1;
14129             opt->type = ComboBox;
14130         } else if(p = strstr(opt->name, " -button")) {
14131             opt->type = Button;
14132         } else if(p = strstr(opt->name, " -save")) {
14133             opt->type = SaveButton;
14134         } else return FALSE;
14135         *p = 0; // terminate option name
14136         // now look if the command-line options define a setting for this engine option.
14137         if(cps->optionSettings && cps->optionSettings[0])
14138             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14139         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14140           snprintf(buf, MSG_SIZ, "option %s", p);
14141                 if(p = strstr(buf, ",")) *p = 0;
14142                 if(q = strchr(buf, '=')) switch(opt->type) {
14143                     case ComboBox:
14144                         for(n=0; n<opt->max; n++)
14145                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14146                         break;
14147                     case TextBox:
14148                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14149                         break;
14150                     case Spin:
14151                     case CheckBox:
14152                         opt->value = atoi(q+1);
14153                     default:
14154                         break;
14155                 }
14156                 strcat(buf, "\n");
14157                 SendToProgram(buf, cps);
14158         }
14159         return TRUE;
14160 }
14161
14162 void
14163 FeatureDone(cps, val)
14164      ChessProgramState* cps;
14165      int val;
14166 {
14167   DelayedEventCallback cb = GetDelayedEvent();
14168   if ((cb == InitBackEnd3 && cps == &first) ||
14169       (cb == SettingsMenuIfReady && cps == &second) ||
14170       (cb == LoadEngine) ||
14171       (cb == TwoMachinesEventIfReady && cps == &second)) {
14172     CancelDelayedEvent();
14173     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14174   }
14175   cps->initDone = val;
14176 }
14177
14178 /* Parse feature command from engine */
14179 void
14180 ParseFeatures(args, cps)
14181      char* args;
14182      ChessProgramState *cps;
14183 {
14184   char *p = args;
14185   char *q;
14186   int val;
14187   char buf[MSG_SIZ];
14188
14189   for (;;) {
14190     while (*p == ' ') p++;
14191     if (*p == NULLCHAR) return;
14192
14193     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14194     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14195     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14196     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14197     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14198     if (BoolFeature(&p, "reuse", &val, cps)) {
14199       /* Engine can disable reuse, but can't enable it if user said no */
14200       if (!val) cps->reuse = FALSE;
14201       continue;
14202     }
14203     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14204     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14205       if (gameMode == TwoMachinesPlay) {
14206         DisplayTwoMachinesTitle();
14207       } else {
14208         DisplayTitle("");
14209       }
14210       continue;
14211     }
14212     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14213     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14214     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14215     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14216     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14217     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14218     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14219     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14220     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14221     if (IntFeature(&p, "done", &val, cps)) {
14222       FeatureDone(cps, val);
14223       continue;
14224     }
14225     /* Added by Tord: */
14226     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14227     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14228     /* End of additions by Tord */
14229
14230     /* [HGM] added features: */
14231     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14232     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14233     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14234     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14235     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14236     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14237     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14238         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14239           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14240             SendToProgram(buf, cps);
14241             continue;
14242         }
14243         if(cps->nrOptions >= MAX_OPTIONS) {
14244             cps->nrOptions--;
14245             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14246             DisplayError(buf, 0);
14247         }
14248         continue;
14249     }
14250     /* End of additions by HGM */
14251
14252     /* unknown feature: complain and skip */
14253     q = p;
14254     while (*q && *q != '=') q++;
14255     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14256     SendToProgram(buf, cps);
14257     p = q;
14258     if (*p == '=') {
14259       p++;
14260       if (*p == '\"') {
14261         p++;
14262         while (*p && *p != '\"') p++;
14263         if (*p == '\"') p++;
14264       } else {
14265         while (*p && *p != ' ') p++;
14266       }
14267     }
14268   }
14269
14270 }
14271
14272 void
14273 PeriodicUpdatesEvent(newState)
14274      int newState;
14275 {
14276     if (newState == appData.periodicUpdates)
14277       return;
14278
14279     appData.periodicUpdates=newState;
14280
14281     /* Display type changes, so update it now */
14282 //    DisplayAnalysis();
14283
14284     /* Get the ball rolling again... */
14285     if (newState) {
14286         AnalysisPeriodicEvent(1);
14287         StartAnalysisClock();
14288     }
14289 }
14290
14291 void
14292 PonderNextMoveEvent(newState)
14293      int newState;
14294 {
14295     if (newState == appData.ponderNextMove) return;
14296     if (gameMode == EditPosition) EditPositionDone(TRUE);
14297     if (newState) {
14298         SendToProgram("hard\n", &first);
14299         if (gameMode == TwoMachinesPlay) {
14300             SendToProgram("hard\n", &second);
14301         }
14302     } else {
14303         SendToProgram("easy\n", &first);
14304         thinkOutput[0] = NULLCHAR;
14305         if (gameMode == TwoMachinesPlay) {
14306             SendToProgram("easy\n", &second);
14307         }
14308     }
14309     appData.ponderNextMove = newState;
14310 }
14311
14312 void
14313 NewSettingEvent(option, feature, command, value)
14314      char *command;
14315      int option, value, *feature;
14316 {
14317     char buf[MSG_SIZ];
14318
14319     if (gameMode == EditPosition) EditPositionDone(TRUE);
14320     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14321     if(feature == NULL || *feature) SendToProgram(buf, &first);
14322     if (gameMode == TwoMachinesPlay) {
14323         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14324     }
14325 }
14326
14327 void
14328 ShowThinkingEvent()
14329 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14330 {
14331     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14332     int newState = appData.showThinking
14333         // [HGM] thinking: other features now need thinking output as well
14334         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14335
14336     if (oldState == newState) return;
14337     oldState = newState;
14338     if (gameMode == EditPosition) EditPositionDone(TRUE);
14339     if (oldState) {
14340         SendToProgram("post\n", &first);
14341         if (gameMode == TwoMachinesPlay) {
14342             SendToProgram("post\n", &second);
14343         }
14344     } else {
14345         SendToProgram("nopost\n", &first);
14346         thinkOutput[0] = NULLCHAR;
14347         if (gameMode == TwoMachinesPlay) {
14348             SendToProgram("nopost\n", &second);
14349         }
14350     }
14351 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14352 }
14353
14354 void
14355 AskQuestionEvent(title, question, replyPrefix, which)
14356      char *title; char *question; char *replyPrefix; char *which;
14357 {
14358   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14359   if (pr == NoProc) return;
14360   AskQuestion(title, question, replyPrefix, pr);
14361 }
14362
14363 void
14364 TypeInEvent(char firstChar)
14365 {
14366     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14367         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14368         gameMode == AnalyzeMode || gameMode == EditGame || \r
14369         gameMode == EditPosition || gameMode == IcsExamining ||\r
14370         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14371         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14372                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14373                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14374         gameMode == Training) PopUpMoveDialog(firstChar);
14375 }
14376
14377 void
14378 TypeInDoneEvent(char *move)
14379 {
14380         Board board;
14381         int n, fromX, fromY, toX, toY;
14382         char promoChar;
14383         ChessMove moveType;\r
14384
14385         // [HGM] FENedit\r
14386         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14387                 EditPositionPasteFEN(move);\r
14388                 return;\r
14389         }\r
14390         // [HGM] movenum: allow move number to be typed in any mode\r
14391         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14392           ToNrEvent(2*n-1);\r
14393           return;\r
14394         }\r
14395
14396       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14397         gameMode != Training) {\r
14398         DisplayMoveError(_("Displayed move is not current"));\r
14399       } else {\r
14400         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14401           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14402         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14403         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14404           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14405           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14406         } else {\r
14407           DisplayMoveError(_("Could not parse move"));\r
14408         }
14409       }\r
14410 }\r
14411
14412 void
14413 DisplayMove(moveNumber)
14414      int moveNumber;
14415 {
14416     char message[MSG_SIZ];
14417     char res[MSG_SIZ];
14418     char cpThinkOutput[MSG_SIZ];
14419
14420     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14421
14422     if (moveNumber == forwardMostMove - 1 ||
14423         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14424
14425         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14426
14427         if (strchr(cpThinkOutput, '\n')) {
14428             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14429         }
14430     } else {
14431         *cpThinkOutput = NULLCHAR;
14432     }
14433
14434     /* [AS] Hide thinking from human user */
14435     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14436         *cpThinkOutput = NULLCHAR;
14437         if( thinkOutput[0] != NULLCHAR ) {
14438             int i;
14439
14440             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14441                 cpThinkOutput[i] = '.';
14442             }
14443             cpThinkOutput[i] = NULLCHAR;
14444             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14445         }
14446     }
14447
14448     if (moveNumber == forwardMostMove - 1 &&
14449         gameInfo.resultDetails != NULL) {
14450         if (gameInfo.resultDetails[0] == NULLCHAR) {
14451           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14452         } else {
14453           snprintf(res, MSG_SIZ, " {%s} %s",
14454                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14455         }
14456     } else {
14457         res[0] = NULLCHAR;
14458     }
14459
14460     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14461         DisplayMessage(res, cpThinkOutput);
14462     } else {
14463       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14464                 WhiteOnMove(moveNumber) ? " " : ".. ",
14465                 parseList[moveNumber], res);
14466         DisplayMessage(message, cpThinkOutput);
14467     }
14468 }
14469
14470 void
14471 DisplayComment(moveNumber, text)
14472      int moveNumber;
14473      char *text;
14474 {
14475     char title[MSG_SIZ];
14476     char buf[8000]; // comment can be long!
14477     int score, depth;
14478
14479     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14480       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14481     } else {
14482       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14483               WhiteOnMove(moveNumber) ? " " : ".. ",
14484               parseList[moveNumber]);
14485     }
14486     // [HGM] PV info: display PV info together with (or as) comment
14487     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14488       if(text == NULL) text = "";
14489       score = pvInfoList[moveNumber].score;
14490       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14491               depth, (pvInfoList[moveNumber].time+50)/100, text);
14492       text = buf;
14493     }
14494     if (text != NULL && (appData.autoDisplayComment || commentUp))
14495         CommentPopUp(title, text);
14496 }
14497
14498 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14499  * might be busy thinking or pondering.  It can be omitted if your
14500  * gnuchess is configured to stop thinking immediately on any user
14501  * input.  However, that gnuchess feature depends on the FIONREAD
14502  * ioctl, which does not work properly on some flavors of Unix.
14503  */
14504 void
14505 Attention(cps)
14506      ChessProgramState *cps;
14507 {
14508 #if ATTENTION
14509     if (!cps->useSigint) return;
14510     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14511     switch (gameMode) {
14512       case MachinePlaysWhite:
14513       case MachinePlaysBlack:
14514       case TwoMachinesPlay:
14515       case IcsPlayingWhite:
14516       case IcsPlayingBlack:
14517       case AnalyzeMode:
14518       case AnalyzeFile:
14519         /* Skip if we know it isn't thinking */
14520         if (!cps->maybeThinking) return;
14521         if (appData.debugMode)
14522           fprintf(debugFP, "Interrupting %s\n", cps->which);
14523         InterruptChildProcess(cps->pr);
14524         cps->maybeThinking = FALSE;
14525         break;
14526       default:
14527         break;
14528     }
14529 #endif /*ATTENTION*/
14530 }
14531
14532 int
14533 CheckFlags()
14534 {
14535     if (whiteTimeRemaining <= 0) {
14536         if (!whiteFlag) {
14537             whiteFlag = TRUE;
14538             if (appData.icsActive) {
14539                 if (appData.autoCallFlag &&
14540                     gameMode == IcsPlayingBlack && !blackFlag) {
14541                   SendToICS(ics_prefix);
14542                   SendToICS("flag\n");
14543                 }
14544             } else {
14545                 if (blackFlag) {
14546                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14547                 } else {
14548                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14549                     if (appData.autoCallFlag) {
14550                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14551                         return TRUE;
14552                     }
14553                 }
14554             }
14555         }
14556     }
14557     if (blackTimeRemaining <= 0) {
14558         if (!blackFlag) {
14559             blackFlag = TRUE;
14560             if (appData.icsActive) {
14561                 if (appData.autoCallFlag &&
14562                     gameMode == IcsPlayingWhite && !whiteFlag) {
14563                   SendToICS(ics_prefix);
14564                   SendToICS("flag\n");
14565                 }
14566             } else {
14567                 if (whiteFlag) {
14568                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14569                 } else {
14570                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14571                     if (appData.autoCallFlag) {
14572                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14573                         return TRUE;
14574                     }
14575                 }
14576             }
14577         }
14578     }
14579     return FALSE;
14580 }
14581
14582 void
14583 CheckTimeControl()
14584 {
14585     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14586         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14587
14588     /*
14589      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14590      */
14591     if ( !WhiteOnMove(forwardMostMove) ) {
14592         /* White made time control */
14593         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14594         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14595         /* [HGM] time odds: correct new time quota for time odds! */
14596                                             / WhitePlayer()->timeOdds;
14597         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14598     } else {
14599         lastBlack -= blackTimeRemaining;
14600         /* Black made time control */
14601         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14602                                             / WhitePlayer()->other->timeOdds;
14603         lastWhite = whiteTimeRemaining;
14604     }
14605 }
14606
14607 void
14608 DisplayBothClocks()
14609 {
14610     int wom = gameMode == EditPosition ?
14611       !blackPlaysFirst : WhiteOnMove(currentMove);
14612     DisplayWhiteClock(whiteTimeRemaining, wom);
14613     DisplayBlackClock(blackTimeRemaining, !wom);
14614 }
14615
14616
14617 /* Timekeeping seems to be a portability nightmare.  I think everyone
14618    has ftime(), but I'm really not sure, so I'm including some ifdefs
14619    to use other calls if you don't.  Clocks will be less accurate if
14620    you have neither ftime nor gettimeofday.
14621 */
14622
14623 /* VS 2008 requires the #include outside of the function */
14624 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14625 #include <sys/timeb.h>
14626 #endif
14627
14628 /* Get the current time as a TimeMark */
14629 void
14630 GetTimeMark(tm)
14631      TimeMark *tm;
14632 {
14633 #if HAVE_GETTIMEOFDAY
14634
14635     struct timeval timeVal;
14636     struct timezone timeZone;
14637
14638     gettimeofday(&timeVal, &timeZone);
14639     tm->sec = (long) timeVal.tv_sec;
14640     tm->ms = (int) (timeVal.tv_usec / 1000L);
14641
14642 #else /*!HAVE_GETTIMEOFDAY*/
14643 #if HAVE_FTIME
14644
14645 // include <sys/timeb.h> / moved to just above start of function
14646     struct timeb timeB;
14647
14648     ftime(&timeB);
14649     tm->sec = (long) timeB.time;
14650     tm->ms = (int) timeB.millitm;
14651
14652 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14653     tm->sec = (long) time(NULL);
14654     tm->ms = 0;
14655 #endif
14656 #endif
14657 }
14658
14659 /* Return the difference in milliseconds between two
14660    time marks.  We assume the difference will fit in a long!
14661 */
14662 long
14663 SubtractTimeMarks(tm2, tm1)
14664      TimeMark *tm2, *tm1;
14665 {
14666     return 1000L*(tm2->sec - tm1->sec) +
14667            (long) (tm2->ms - tm1->ms);
14668 }
14669
14670
14671 /*
14672  * Code to manage the game clocks.
14673  *
14674  * In tournament play, black starts the clock and then white makes a move.
14675  * We give the human user a slight advantage if he is playing white---the
14676  * clocks don't run until he makes his first move, so it takes zero time.
14677  * Also, we don't account for network lag, so we could get out of sync
14678  * with GNU Chess's clock -- but then, referees are always right.
14679  */
14680
14681 static TimeMark tickStartTM;
14682 static long intendedTickLength;
14683
14684 long
14685 NextTickLength(timeRemaining)
14686      long timeRemaining;
14687 {
14688     long nominalTickLength, nextTickLength;
14689
14690     if (timeRemaining > 0L && timeRemaining <= 10000L)
14691       nominalTickLength = 100L;
14692     else
14693       nominalTickLength = 1000L;
14694     nextTickLength = timeRemaining % nominalTickLength;
14695     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14696
14697     return nextTickLength;
14698 }
14699
14700 /* Adjust clock one minute up or down */
14701 void
14702 AdjustClock(Boolean which, int dir)
14703 {
14704     if(which) blackTimeRemaining += 60000*dir;
14705     else      whiteTimeRemaining += 60000*dir;
14706     DisplayBothClocks();
14707 }
14708
14709 /* Stop clocks and reset to a fresh time control */
14710 void
14711 ResetClocks()
14712 {
14713     (void) StopClockTimer();
14714     if (appData.icsActive) {
14715         whiteTimeRemaining = blackTimeRemaining = 0;
14716     } else if (searchTime) {
14717         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14718         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14719     } else { /* [HGM] correct new time quote for time odds */
14720         whiteTC = blackTC = fullTimeControlString;
14721         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14722         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14723     }
14724     if (whiteFlag || blackFlag) {
14725         DisplayTitle("");
14726         whiteFlag = blackFlag = FALSE;
14727     }
14728     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14729     DisplayBothClocks();
14730 }
14731
14732 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14733
14734 /* Decrement running clock by amount of time that has passed */
14735 void
14736 DecrementClocks()
14737 {
14738     long timeRemaining;
14739     long lastTickLength, fudge;
14740     TimeMark now;
14741
14742     if (!appData.clockMode) return;
14743     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14744
14745     GetTimeMark(&now);
14746
14747     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14748
14749     /* Fudge if we woke up a little too soon */
14750     fudge = intendedTickLength - lastTickLength;
14751     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14752
14753     if (WhiteOnMove(forwardMostMove)) {
14754         if(whiteNPS >= 0) lastTickLength = 0;
14755         timeRemaining = whiteTimeRemaining -= lastTickLength;
14756         if(timeRemaining < 0 && !appData.icsActive) {
14757             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14758             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14759                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14760                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14761             }
14762         }
14763         DisplayWhiteClock(whiteTimeRemaining - fudge,
14764                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14765     } else {
14766         if(blackNPS >= 0) lastTickLength = 0;
14767         timeRemaining = blackTimeRemaining -= lastTickLength;
14768         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14769             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14770             if(suddenDeath) {
14771                 blackStartMove = forwardMostMove;
14772                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14773             }
14774         }
14775         DisplayBlackClock(blackTimeRemaining - fudge,
14776                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14777     }
14778     if (CheckFlags()) return;
14779
14780     tickStartTM = now;
14781     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14782     StartClockTimer(intendedTickLength);
14783
14784     /* if the time remaining has fallen below the alarm threshold, sound the
14785      * alarm. if the alarm has sounded and (due to a takeback or time control
14786      * with increment) the time remaining has increased to a level above the
14787      * threshold, reset the alarm so it can sound again.
14788      */
14789
14790     if (appData.icsActive && appData.icsAlarm) {
14791
14792         /* make sure we are dealing with the user's clock */
14793         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14794                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14795            )) return;
14796
14797         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14798             alarmSounded = FALSE;
14799         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14800             PlayAlarmSound();
14801             alarmSounded = TRUE;
14802         }
14803     }
14804 }
14805
14806
14807 /* A player has just moved, so stop the previously running
14808    clock and (if in clock mode) start the other one.
14809    We redisplay both clocks in case we're in ICS mode, because
14810    ICS gives us an update to both clocks after every move.
14811    Note that this routine is called *after* forwardMostMove
14812    is updated, so the last fractional tick must be subtracted
14813    from the color that is *not* on move now.
14814 */
14815 void
14816 SwitchClocks(int newMoveNr)
14817 {
14818     long lastTickLength;
14819     TimeMark now;
14820     int flagged = FALSE;
14821
14822     GetTimeMark(&now);
14823
14824     if (StopClockTimer() && appData.clockMode) {
14825         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14826         if (!WhiteOnMove(forwardMostMove)) {
14827             if(blackNPS >= 0) lastTickLength = 0;
14828             blackTimeRemaining -= lastTickLength;
14829            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14830 //         if(pvInfoList[forwardMostMove].time == -1)
14831                  pvInfoList[forwardMostMove].time =               // use GUI time
14832                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14833         } else {
14834            if(whiteNPS >= 0) lastTickLength = 0;
14835            whiteTimeRemaining -= lastTickLength;
14836            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14837 //         if(pvInfoList[forwardMostMove].time == -1)
14838                  pvInfoList[forwardMostMove].time =
14839                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14840         }
14841         flagged = CheckFlags();
14842     }
14843     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14844     CheckTimeControl();
14845
14846     if (flagged || !appData.clockMode) return;
14847
14848     switch (gameMode) {
14849       case MachinePlaysBlack:
14850       case MachinePlaysWhite:
14851       case BeginningOfGame:
14852         if (pausing) return;
14853         break;
14854
14855       case EditGame:
14856       case PlayFromGameFile:
14857       case IcsExamining:
14858         return;
14859
14860       default:
14861         break;
14862     }
14863
14864     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14865         if(WhiteOnMove(forwardMostMove))
14866              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14867         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14868     }
14869
14870     tickStartTM = now;
14871     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14872       whiteTimeRemaining : blackTimeRemaining);
14873     StartClockTimer(intendedTickLength);
14874 }
14875
14876
14877 /* Stop both clocks */
14878 void
14879 StopClocks()
14880 {
14881     long lastTickLength;
14882     TimeMark now;
14883
14884     if (!StopClockTimer()) return;
14885     if (!appData.clockMode) return;
14886
14887     GetTimeMark(&now);
14888
14889     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14890     if (WhiteOnMove(forwardMostMove)) {
14891         if(whiteNPS >= 0) lastTickLength = 0;
14892         whiteTimeRemaining -= lastTickLength;
14893         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14894     } else {
14895         if(blackNPS >= 0) lastTickLength = 0;
14896         blackTimeRemaining -= lastTickLength;
14897         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14898     }
14899     CheckFlags();
14900 }
14901
14902 /* Start clock of player on move.  Time may have been reset, so
14903    if clock is already running, stop and restart it. */
14904 void
14905 StartClocks()
14906 {
14907     (void) StopClockTimer(); /* in case it was running already */
14908     DisplayBothClocks();
14909     if (CheckFlags()) return;
14910
14911     if (!appData.clockMode) return;
14912     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14913
14914     GetTimeMark(&tickStartTM);
14915     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14916       whiteTimeRemaining : blackTimeRemaining);
14917
14918    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14919     whiteNPS = blackNPS = -1;
14920     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14921        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14922         whiteNPS = first.nps;
14923     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14924        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14925         blackNPS = first.nps;
14926     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14927         whiteNPS = second.nps;
14928     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14929         blackNPS = second.nps;
14930     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14931
14932     StartClockTimer(intendedTickLength);
14933 }
14934
14935 char *
14936 TimeString(ms)
14937      long ms;
14938 {
14939     long second, minute, hour, day;
14940     char *sign = "";
14941     static char buf[32];
14942
14943     if (ms > 0 && ms <= 9900) {
14944       /* convert milliseconds to tenths, rounding up */
14945       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14946
14947       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14948       return buf;
14949     }
14950
14951     /* convert milliseconds to seconds, rounding up */
14952     /* use floating point to avoid strangeness of integer division
14953        with negative dividends on many machines */
14954     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14955
14956     if (second < 0) {
14957         sign = "-";
14958         second = -second;
14959     }
14960
14961     day = second / (60 * 60 * 24);
14962     second = second % (60 * 60 * 24);
14963     hour = second / (60 * 60);
14964     second = second % (60 * 60);
14965     minute = second / 60;
14966     second = second % 60;
14967
14968     if (day > 0)
14969       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14970               sign, day, hour, minute, second);
14971     else if (hour > 0)
14972       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14973     else
14974       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14975
14976     return buf;
14977 }
14978
14979
14980 /*
14981  * This is necessary because some C libraries aren't ANSI C compliant yet.
14982  */
14983 char *
14984 StrStr(string, match)
14985      char *string, *match;
14986 {
14987     int i, length;
14988
14989     length = strlen(match);
14990
14991     for (i = strlen(string) - length; i >= 0; i--, string++)
14992       if (!strncmp(match, string, length))
14993         return string;
14994
14995     return NULL;
14996 }
14997
14998 char *
14999 StrCaseStr(string, match)
15000      char *string, *match;
15001 {
15002     int i, j, length;
15003
15004     length = strlen(match);
15005
15006     for (i = strlen(string) - length; i >= 0; i--, string++) {
15007         for (j = 0; j < length; j++) {
15008             if (ToLower(match[j]) != ToLower(string[j]))
15009               break;
15010         }
15011         if (j == length) return string;
15012     }
15013
15014     return NULL;
15015 }
15016
15017 #ifndef _amigados
15018 int
15019 StrCaseCmp(s1, s2)
15020      char *s1, *s2;
15021 {
15022     char c1, c2;
15023
15024     for (;;) {
15025         c1 = ToLower(*s1++);
15026         c2 = ToLower(*s2++);
15027         if (c1 > c2) return 1;
15028         if (c1 < c2) return -1;
15029         if (c1 == NULLCHAR) return 0;
15030     }
15031 }
15032
15033
15034 int
15035 ToLower(c)
15036      int c;
15037 {
15038     return isupper(c) ? tolower(c) : c;
15039 }
15040
15041
15042 int
15043 ToUpper(c)
15044      int c;
15045 {
15046     return islower(c) ? toupper(c) : c;
15047 }
15048 #endif /* !_amigados    */
15049
15050 char *
15051 StrSave(s)
15052      char *s;
15053 {
15054   char *ret;
15055
15056   if ((ret = (char *) malloc(strlen(s) + 1)))
15057     {
15058       safeStrCpy(ret, s, strlen(s)+1);
15059     }
15060   return ret;
15061 }
15062
15063 char *
15064 StrSavePtr(s, savePtr)
15065      char *s, **savePtr;
15066 {
15067     if (*savePtr) {
15068         free(*savePtr);
15069     }
15070     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15071       safeStrCpy(*savePtr, s, strlen(s)+1);
15072     }
15073     return(*savePtr);
15074 }
15075
15076 char *
15077 PGNDate()
15078 {
15079     time_t clock;
15080     struct tm *tm;
15081     char buf[MSG_SIZ];
15082
15083     clock = time((time_t *)NULL);
15084     tm = localtime(&clock);
15085     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15086             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15087     return StrSave(buf);
15088 }
15089
15090
15091 char *
15092 PositionToFEN(move, overrideCastling)
15093      int move;
15094      char *overrideCastling;
15095 {
15096     int i, j, fromX, fromY, toX, toY;
15097     int whiteToPlay;
15098     char buf[128];
15099     char *p, *q;
15100     int emptycount;
15101     ChessSquare piece;
15102
15103     whiteToPlay = (gameMode == EditPosition) ?
15104       !blackPlaysFirst : (move % 2 == 0);
15105     p = buf;
15106
15107     /* Piece placement data */
15108     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15109         emptycount = 0;
15110         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15111             if (boards[move][i][j] == EmptySquare) {
15112                 emptycount++;
15113             } else { ChessSquare piece = boards[move][i][j];
15114                 if (emptycount > 0) {
15115                     if(emptycount<10) /* [HGM] can be >= 10 */
15116                         *p++ = '0' + emptycount;
15117                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15118                     emptycount = 0;
15119                 }
15120                 if(PieceToChar(piece) == '+') {
15121                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15122                     *p++ = '+';
15123                     piece = (ChessSquare)(DEMOTED piece);
15124                 }
15125                 *p++ = PieceToChar(piece);
15126                 if(p[-1] == '~') {
15127                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15128                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15129                     *p++ = '~';
15130                 }
15131             }
15132         }
15133         if (emptycount > 0) {
15134             if(emptycount<10) /* [HGM] can be >= 10 */
15135                 *p++ = '0' + emptycount;
15136             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15137             emptycount = 0;
15138         }
15139         *p++ = '/';
15140     }
15141     *(p - 1) = ' ';
15142
15143     /* [HGM] print Crazyhouse or Shogi holdings */
15144     if( gameInfo.holdingsWidth ) {
15145         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15146         q = p;
15147         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15148             piece = boards[move][i][BOARD_WIDTH-1];
15149             if( piece != EmptySquare )
15150               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15151                   *p++ = PieceToChar(piece);
15152         }
15153         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15154             piece = boards[move][BOARD_HEIGHT-i-1][0];
15155             if( piece != EmptySquare )
15156               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15157                   *p++ = PieceToChar(piece);
15158         }
15159
15160         if( q == p ) *p++ = '-';
15161         *p++ = ']';
15162         *p++ = ' ';
15163     }
15164
15165     /* Active color */
15166     *p++ = whiteToPlay ? 'w' : 'b';
15167     *p++ = ' ';
15168
15169   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15170     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15171   } else {
15172   if(nrCastlingRights) {
15173      q = p;
15174      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15175        /* [HGM] write directly from rights */
15176            if(boards[move][CASTLING][2] != NoRights &&
15177               boards[move][CASTLING][0] != NoRights   )
15178                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15179            if(boards[move][CASTLING][2] != NoRights &&
15180               boards[move][CASTLING][1] != NoRights   )
15181                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15182            if(boards[move][CASTLING][5] != NoRights &&
15183               boards[move][CASTLING][3] != NoRights   )
15184                 *p++ = boards[move][CASTLING][3] + AAA;
15185            if(boards[move][CASTLING][5] != NoRights &&
15186               boards[move][CASTLING][4] != NoRights   )
15187                 *p++ = boards[move][CASTLING][4] + AAA;
15188      } else {
15189
15190         /* [HGM] write true castling rights */
15191         if( nrCastlingRights == 6 ) {
15192             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15193                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15194             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15195                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15196             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15197                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15198             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15199                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15200         }
15201      }
15202      if (q == p) *p++ = '-'; /* No castling rights */
15203      *p++ = ' ';
15204   }
15205
15206   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15207      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15208     /* En passant target square */
15209     if (move > backwardMostMove) {
15210         fromX = moveList[move - 1][0] - AAA;
15211         fromY = moveList[move - 1][1] - ONE;
15212         toX = moveList[move - 1][2] - AAA;
15213         toY = moveList[move - 1][3] - ONE;
15214         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15215             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15216             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15217             fromX == toX) {
15218             /* 2-square pawn move just happened */
15219             *p++ = toX + AAA;
15220             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15221         } else {
15222             *p++ = '-';
15223         }
15224     } else if(move == backwardMostMove) {
15225         // [HGM] perhaps we should always do it like this, and forget the above?
15226         if((signed char)boards[move][EP_STATUS] >= 0) {
15227             *p++ = boards[move][EP_STATUS] + AAA;
15228             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15229         } else {
15230             *p++ = '-';
15231         }
15232     } else {
15233         *p++ = '-';
15234     }
15235     *p++ = ' ';
15236   }
15237   }
15238
15239     /* [HGM] find reversible plies */
15240     {   int i = 0, j=move;
15241
15242         if (appData.debugMode) { int k;
15243             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15244             for(k=backwardMostMove; k<=forwardMostMove; k++)
15245                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15246
15247         }
15248
15249         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15250         if( j == backwardMostMove ) i += initialRulePlies;
15251         sprintf(p, "%d ", i);
15252         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15253     }
15254     /* Fullmove number */
15255     sprintf(p, "%d", (move / 2) + 1);
15256
15257     return StrSave(buf);
15258 }
15259
15260 Boolean
15261 ParseFEN(board, blackPlaysFirst, fen)
15262     Board board;
15263      int *blackPlaysFirst;
15264      char *fen;
15265 {
15266     int i, j;
15267     char *p, c;
15268     int emptycount;
15269     ChessSquare piece;
15270
15271     p = fen;
15272
15273     /* [HGM] by default clear Crazyhouse holdings, if present */
15274     if(gameInfo.holdingsWidth) {
15275        for(i=0; i<BOARD_HEIGHT; i++) {
15276            board[i][0]             = EmptySquare; /* black holdings */
15277            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15278            board[i][1]             = (ChessSquare) 0; /* black counts */
15279            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15280        }
15281     }
15282
15283     /* Piece placement data */
15284     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15285         j = 0;
15286         for (;;) {
15287             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15288                 if (*p == '/') p++;
15289                 emptycount = gameInfo.boardWidth - j;
15290                 while (emptycount--)
15291                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15292                 break;
15293 #if(BOARD_FILES >= 10)
15294             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15295                 p++; emptycount=10;
15296                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15297                 while (emptycount--)
15298                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15299 #endif
15300             } else if (isdigit(*p)) {
15301                 emptycount = *p++ - '0';
15302                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15303                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15304                 while (emptycount--)
15305                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15306             } else if (*p == '+' || isalpha(*p)) {
15307                 if (j >= gameInfo.boardWidth) return FALSE;
15308                 if(*p=='+') {
15309                     piece = CharToPiece(*++p);
15310                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15311                     piece = (ChessSquare) (PROMOTED piece ); p++;
15312                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15313                 } else piece = CharToPiece(*p++);
15314
15315                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15316                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15317                     piece = (ChessSquare) (PROMOTED piece);
15318                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15319                     p++;
15320                 }
15321                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15322             } else {
15323                 return FALSE;
15324             }
15325         }
15326     }
15327     while (*p == '/' || *p == ' ') p++;
15328
15329     /* [HGM] look for Crazyhouse holdings here */
15330     while(*p==' ') p++;
15331     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15332         if(*p == '[') p++;
15333         if(*p == '-' ) p++; /* empty holdings */ else {
15334             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15335             /* if we would allow FEN reading to set board size, we would   */
15336             /* have to add holdings and shift the board read so far here   */
15337             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15338                 p++;
15339                 if((int) piece >= (int) BlackPawn ) {
15340                     i = (int)piece - (int)BlackPawn;
15341                     i = PieceToNumber((ChessSquare)i);
15342                     if( i >= gameInfo.holdingsSize ) return FALSE;
15343                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15344                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15345                 } else {
15346                     i = (int)piece - (int)WhitePawn;
15347                     i = PieceToNumber((ChessSquare)i);
15348                     if( i >= gameInfo.holdingsSize ) return FALSE;
15349                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15350                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15351                 }
15352             }
15353         }
15354         if(*p == ']') p++;
15355     }
15356
15357     while(*p == ' ') p++;
15358
15359     /* Active color */
15360     c = *p++;
15361     if(appData.colorNickNames) {
15362       if( c == appData.colorNickNames[0] ) c = 'w'; else
15363       if( c == appData.colorNickNames[1] ) c = 'b';
15364     }
15365     switch (c) {
15366       case 'w':
15367         *blackPlaysFirst = FALSE;
15368         break;
15369       case 'b':
15370         *blackPlaysFirst = TRUE;
15371         break;
15372       default:
15373         return FALSE;
15374     }
15375
15376     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15377     /* return the extra info in global variiables             */
15378
15379     /* set defaults in case FEN is incomplete */
15380     board[EP_STATUS] = EP_UNKNOWN;
15381     for(i=0; i<nrCastlingRights; i++ ) {
15382         board[CASTLING][i] =
15383             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15384     }   /* assume possible unless obviously impossible */
15385     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15386     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15387     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15388                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15389     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15390     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15391     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15392                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15393     FENrulePlies = 0;
15394
15395     while(*p==' ') p++;
15396     if(nrCastlingRights) {
15397       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15398           /* castling indicator present, so default becomes no castlings */
15399           for(i=0; i<nrCastlingRights; i++ ) {
15400                  board[CASTLING][i] = NoRights;
15401           }
15402       }
15403       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15404              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15405              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15406              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15407         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15408
15409         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15410             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15411             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15412         }
15413         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15414             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15415         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15416                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15417         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15418                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15419         switch(c) {
15420           case'K':
15421               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15422               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15423               board[CASTLING][2] = whiteKingFile;
15424               break;
15425           case'Q':
15426               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15427               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15428               board[CASTLING][2] = whiteKingFile;
15429               break;
15430           case'k':
15431               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15432               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15433               board[CASTLING][5] = blackKingFile;
15434               break;
15435           case'q':
15436               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15437               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15438               board[CASTLING][5] = blackKingFile;
15439           case '-':
15440               break;
15441           default: /* FRC castlings */
15442               if(c >= 'a') { /* black rights */
15443                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15444                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15445                   if(i == BOARD_RGHT) break;
15446                   board[CASTLING][5] = i;
15447                   c -= AAA;
15448                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15449                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15450                   if(c > i)
15451                       board[CASTLING][3] = c;
15452                   else
15453                       board[CASTLING][4] = c;
15454               } else { /* white rights */
15455                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15456                     if(board[0][i] == WhiteKing) break;
15457                   if(i == BOARD_RGHT) break;
15458                   board[CASTLING][2] = i;
15459                   c -= AAA - 'a' + 'A';
15460                   if(board[0][c] >= WhiteKing) break;
15461                   if(c > i)
15462                       board[CASTLING][0] = c;
15463                   else
15464                       board[CASTLING][1] = c;
15465               }
15466         }
15467       }
15468       for(i=0; i<nrCastlingRights; i++)
15469         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15470     if (appData.debugMode) {
15471         fprintf(debugFP, "FEN castling rights:");
15472         for(i=0; i<nrCastlingRights; i++)
15473         fprintf(debugFP, " %d", board[CASTLING][i]);
15474         fprintf(debugFP, "\n");
15475     }
15476
15477       while(*p==' ') p++;
15478     }
15479
15480     /* read e.p. field in games that know e.p. capture */
15481     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15482        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15483       if(*p=='-') {
15484         p++; board[EP_STATUS] = EP_NONE;
15485       } else {
15486          char c = *p++ - AAA;
15487
15488          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15489          if(*p >= '0' && *p <='9') p++;
15490          board[EP_STATUS] = c;
15491       }
15492     }
15493
15494
15495     if(sscanf(p, "%d", &i) == 1) {
15496         FENrulePlies = i; /* 50-move ply counter */
15497         /* (The move number is still ignored)    */
15498     }
15499
15500     return TRUE;
15501 }
15502
15503 void
15504 EditPositionPasteFEN(char *fen)
15505 {
15506   if (fen != NULL) {
15507     Board initial_position;
15508
15509     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15510       DisplayError(_("Bad FEN position in clipboard"), 0);
15511       return ;
15512     } else {
15513       int savedBlackPlaysFirst = blackPlaysFirst;
15514       EditPositionEvent();
15515       blackPlaysFirst = savedBlackPlaysFirst;
15516       CopyBoard(boards[0], initial_position);
15517       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15518       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15519       DisplayBothClocks();
15520       DrawPosition(FALSE, boards[currentMove]);
15521     }
15522   }
15523 }
15524
15525 static char cseq[12] = "\\   ";
15526
15527 Boolean set_cont_sequence(char *new_seq)
15528 {
15529     int len;
15530     Boolean ret;
15531
15532     // handle bad attempts to set the sequence
15533         if (!new_seq)
15534                 return 0; // acceptable error - no debug
15535
15536     len = strlen(new_seq);
15537     ret = (len > 0) && (len < sizeof(cseq));
15538     if (ret)
15539       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15540     else if (appData.debugMode)
15541       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15542     return ret;
15543 }
15544
15545 /*
15546     reformat a source message so words don't cross the width boundary.  internal
15547     newlines are not removed.  returns the wrapped size (no null character unless
15548     included in source message).  If dest is NULL, only calculate the size required
15549     for the dest buffer.  lp argument indicats line position upon entry, and it's
15550     passed back upon exit.
15551 */
15552 int wrap(char *dest, char *src, int count, int width, int *lp)
15553 {
15554     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15555
15556     cseq_len = strlen(cseq);
15557     old_line = line = *lp;
15558     ansi = len = clen = 0;
15559
15560     for (i=0; i < count; i++)
15561     {
15562         if (src[i] == '\033')
15563             ansi = 1;
15564
15565         // if we hit the width, back up
15566         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15567         {
15568             // store i & len in case the word is too long
15569             old_i = i, old_len = len;
15570
15571             // find the end of the last word
15572             while (i && src[i] != ' ' && src[i] != '\n')
15573             {
15574                 i--;
15575                 len--;
15576             }
15577
15578             // word too long?  restore i & len before splitting it
15579             if ((old_i-i+clen) >= width)
15580             {
15581                 i = old_i;
15582                 len = old_len;
15583             }
15584
15585             // extra space?
15586             if (i && src[i-1] == ' ')
15587                 len--;
15588
15589             if (src[i] != ' ' && src[i] != '\n')
15590             {
15591                 i--;
15592                 if (len)
15593                     len--;
15594             }
15595
15596             // now append the newline and continuation sequence
15597             if (dest)
15598                 dest[len] = '\n';
15599             len++;
15600             if (dest)
15601                 strncpy(dest+len, cseq, cseq_len);
15602             len += cseq_len;
15603             line = cseq_len;
15604             clen = cseq_len;
15605             continue;
15606         }
15607
15608         if (dest)
15609             dest[len] = src[i];
15610         len++;
15611         if (!ansi)
15612             line++;
15613         if (src[i] == '\n')
15614             line = 0;
15615         if (src[i] == 'm')
15616             ansi = 0;
15617     }
15618     if (dest && appData.debugMode)
15619     {
15620         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15621             count, width, line, len, *lp);
15622         show_bytes(debugFP, src, count);
15623         fprintf(debugFP, "\ndest: ");
15624         show_bytes(debugFP, dest, len);
15625         fprintf(debugFP, "\n");
15626     }
15627     *lp = dest ? line : old_line;
15628
15629     return len;
15630 }
15631
15632 // [HGM] vari: routines for shelving variations
15633
15634 void
15635 PushTail(int firstMove, int lastMove)
15636 {
15637         int i, j, nrMoves = lastMove - firstMove;
15638
15639         if(appData.icsActive) { // only in local mode
15640                 forwardMostMove = currentMove; // mimic old ICS behavior
15641                 return;
15642         }
15643         if(storedGames >= MAX_VARIATIONS-1) return;
15644
15645         // push current tail of game on stack
15646         savedResult[storedGames] = gameInfo.result;
15647         savedDetails[storedGames] = gameInfo.resultDetails;
15648         gameInfo.resultDetails = NULL;
15649         savedFirst[storedGames] = firstMove;
15650         savedLast [storedGames] = lastMove;
15651         savedFramePtr[storedGames] = framePtr;
15652         framePtr -= nrMoves; // reserve space for the boards
15653         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15654             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15655             for(j=0; j<MOVE_LEN; j++)
15656                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15657             for(j=0; j<2*MOVE_LEN; j++)
15658                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15659             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15660             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15661             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15662             pvInfoList[firstMove+i-1].depth = 0;
15663             commentList[framePtr+i] = commentList[firstMove+i];
15664             commentList[firstMove+i] = NULL;
15665         }
15666
15667         storedGames++;
15668         forwardMostMove = firstMove; // truncate game so we can start variation
15669         if(storedGames == 1) GreyRevert(FALSE);
15670 }
15671
15672 Boolean
15673 PopTail(Boolean annotate)
15674 {
15675         int i, j, nrMoves;
15676         char buf[8000], moveBuf[20];
15677
15678         if(appData.icsActive) return FALSE; // only in local mode
15679         if(!storedGames) return FALSE; // sanity
15680         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15681
15682         storedGames--;
15683         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15684         nrMoves = savedLast[storedGames] - currentMove;
15685         if(annotate) {
15686                 int cnt = 10;
15687                 if(!WhiteOnMove(currentMove))
15688                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15689                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15690                 for(i=currentMove; i<forwardMostMove; i++) {
15691                         if(WhiteOnMove(i))
15692                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15693                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15694                         strcat(buf, moveBuf);
15695                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15696                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15697                 }
15698                 strcat(buf, ")");
15699         }
15700         for(i=1; i<=nrMoves; i++) { // copy last variation back
15701             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15702             for(j=0; j<MOVE_LEN; j++)
15703                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15704             for(j=0; j<2*MOVE_LEN; j++)
15705                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15706             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15707             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15708             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15709             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15710             commentList[currentMove+i] = commentList[framePtr+i];
15711             commentList[framePtr+i] = NULL;
15712         }
15713         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15714         framePtr = savedFramePtr[storedGames];
15715         gameInfo.result = savedResult[storedGames];
15716         if(gameInfo.resultDetails != NULL) {
15717             free(gameInfo.resultDetails);
15718       }
15719         gameInfo.resultDetails = savedDetails[storedGames];
15720         forwardMostMove = currentMove + nrMoves;
15721         if(storedGames == 0) GreyRevert(TRUE);
15722         return TRUE;
15723 }
15724
15725 void
15726 CleanupTail()
15727 {       // remove all shelved variations
15728         int i;
15729         for(i=0; i<storedGames; i++) {
15730             if(savedDetails[i])
15731                 free(savedDetails[i]);
15732             savedDetails[i] = NULL;
15733         }
15734         for(i=framePtr; i<MAX_MOVES; i++) {
15735                 if(commentList[i]) free(commentList[i]);
15736                 commentList[i] = NULL;
15737         }
15738         framePtr = MAX_MOVES-1;
15739         storedGames = 0;
15740 }
15741
15742 void
15743 LoadVariation(int index, char *text)
15744 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15745         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15746         int level = 0, move;
15747
15748         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15749         // first find outermost bracketing variation
15750         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15751             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15752                 if(*p == '{') wait = '}'; else
15753                 if(*p == '[') wait = ']'; else
15754                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15755                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15756             }
15757             if(*p == wait) wait = NULLCHAR; // closing ]} found
15758             p++;
15759         }
15760         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15761         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15762         end[1] = NULLCHAR; // clip off comment beyond variation
15763         ToNrEvent(currentMove-1);
15764         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15765         // kludge: use ParsePV() to append variation to game
15766         move = currentMove;
15767         ParsePV(start, TRUE);
15768         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15769         ClearPremoveHighlights();
15770         CommentPopDown();
15771         ToNrEvent(currentMove+1);
15772 }
15773