added/fixed i18n support via gettext to xboard
[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       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1324         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1325         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1326         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1327         ModeHighlight();
1328       }
1329     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1330       initialMode = TwoMachinesPlay;
1331     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1332       initialMode = AnalyzeFile;
1333     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1334       initialMode = AnalyzeMode;
1335     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1336       initialMode = MachinePlaysWhite;
1337     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1338       initialMode = MachinePlaysBlack;
1339     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1340       initialMode = EditGame;
1341     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1342       initialMode = EditPosition;
1343     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1344       initialMode = Training;
1345     } else {
1346       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1347       if( (len > MSG_SIZ) && appData.debugMode )
1348         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1349
1350       DisplayFatalError(buf, 0, 2);
1351       return;
1352     }
1353
1354     if (appData.matchMode) {
1355         MatchEvent(TRUE);
1356     } else if (*appData.cmailGameName != NULLCHAR) {
1357         /* Set up cmail mode */
1358         ReloadCmailMsgEvent(TRUE);
1359     } else {
1360         /* Set up other modes */
1361         if (initialMode == AnalyzeFile) {
1362           if (*appData.loadGameFile == NULLCHAR) {
1363             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1364             return;
1365           }
1366         }
1367         if (*appData.loadGameFile != NULLCHAR) {
1368             (void) LoadGameFromFile(appData.loadGameFile,
1369                                     appData.loadGameIndex,
1370                                     appData.loadGameFile, TRUE);
1371         } else if (*appData.loadPositionFile != NULLCHAR) {
1372             (void) LoadPositionFromFile(appData.loadPositionFile,
1373                                         appData.loadPositionIndex,
1374                                         appData.loadPositionFile);
1375             /* [HGM] try to make self-starting even after FEN load */
1376             /* to allow automatic setup of fairy variants with wtm */
1377             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1378                 gameMode = BeginningOfGame;
1379                 setboardSpoiledMachineBlack = 1;
1380             }
1381             /* [HGM] loadPos: make that every new game uses the setup */
1382             /* from file as long as we do not switch variant          */
1383             if(!blackPlaysFirst) {
1384                 startedFromPositionFile = TRUE;
1385                 CopyBoard(filePosition, boards[0]);
1386             }
1387         }
1388         if (initialMode == AnalyzeMode) {
1389           if (appData.noChessProgram) {
1390             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1391             return;
1392           }
1393           if (appData.icsActive) {
1394             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1395             return;
1396           }
1397           AnalyzeModeEvent();
1398         } else if (initialMode == AnalyzeFile) {
1399           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1400           ShowThinkingEvent();
1401           AnalyzeFileEvent();
1402           AnalysisPeriodicEvent(1);
1403         } else if (initialMode == MachinePlaysWhite) {
1404           if (appData.noChessProgram) {
1405             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1406                               0, 2);
1407             return;
1408           }
1409           if (appData.icsActive) {
1410             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1411                               0, 2);
1412             return;
1413           }
1414           MachineWhiteEvent();
1415         } else if (initialMode == MachinePlaysBlack) {
1416           if (appData.noChessProgram) {
1417             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1418                               0, 2);
1419             return;
1420           }
1421           if (appData.icsActive) {
1422             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1423                               0, 2);
1424             return;
1425           }
1426           MachineBlackEvent();
1427         } else if (initialMode == TwoMachinesPlay) {
1428           if (appData.noChessProgram) {
1429             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1430                               0, 2);
1431             return;
1432           }
1433           if (appData.icsActive) {
1434             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1435                               0, 2);
1436             return;
1437           }
1438           TwoMachinesEvent();
1439         } else if (initialMode == EditGame) {
1440           EditGameEvent();
1441         } else if (initialMode == EditPosition) {
1442           EditPositionEvent();
1443         } else if (initialMode == Training) {
1444           if (*appData.loadGameFile == NULLCHAR) {
1445             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1446             return;
1447           }
1448           TrainingEvent();
1449         }
1450     }
1451 }
1452
1453 /*
1454  * Establish will establish a contact to a remote host.port.
1455  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1456  *  used to talk to the host.
1457  * Returns 0 if okay, error code if not.
1458  */
1459 int
1460 establish()
1461 {
1462     char buf[MSG_SIZ];
1463
1464     if (*appData.icsCommPort != NULLCHAR) {
1465         /* Talk to the host through a serial comm port */
1466         return OpenCommPort(appData.icsCommPort, &icsPR);
1467
1468     } else if (*appData.gateway != NULLCHAR) {
1469         if (*appData.remoteShell == NULLCHAR) {
1470             /* Use the rcmd protocol to run telnet program on a gateway host */
1471             snprintf(buf, sizeof(buf), "%s %s %s",
1472                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1473             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1474
1475         } else {
1476             /* Use the rsh program to run telnet program on a gateway host */
1477             if (*appData.remoteUser == NULLCHAR) {
1478                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1479                         appData.gateway, appData.telnetProgram,
1480                         appData.icsHost, appData.icsPort);
1481             } else {
1482                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1483                         appData.remoteShell, appData.gateway,
1484                         appData.remoteUser, appData.telnetProgram,
1485                         appData.icsHost, appData.icsPort);
1486             }
1487             return StartChildProcess(buf, "", &icsPR);
1488
1489         }
1490     } else if (appData.useTelnet) {
1491         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1492
1493     } else {
1494         /* TCP socket interface differs somewhat between
1495            Unix and NT; handle details in the front end.
1496            */
1497         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1498     }
1499 }
1500
1501 void EscapeExpand(char *p, char *q)
1502 {       // [HGM] initstring: routine to shape up string arguments
1503         while(*p++ = *q++) if(p[-1] == '\\')
1504             switch(*q++) {
1505                 case 'n': p[-1] = '\n'; break;
1506                 case 'r': p[-1] = '\r'; break;
1507                 case 't': p[-1] = '\t'; break;
1508                 case '\\': p[-1] = '\\'; break;
1509                 case 0: *p = 0; return;
1510                 default: p[-1] = q[-1]; break;
1511             }
1512 }
1513
1514 void
1515 show_bytes(fp, buf, count)
1516      FILE *fp;
1517      char *buf;
1518      int count;
1519 {
1520     while (count--) {
1521         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1522             fprintf(fp, "\\%03o", *buf & 0xff);
1523         } else {
1524             putc(*buf, fp);
1525         }
1526         buf++;
1527     }
1528     fflush(fp);
1529 }
1530
1531 /* Returns an errno value */
1532 int
1533 OutputMaybeTelnet(pr, message, count, outError)
1534      ProcRef pr;
1535      char *message;
1536      int count;
1537      int *outError;
1538 {
1539     char buf[8192], *p, *q, *buflim;
1540     int left, newcount, outcount;
1541
1542     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1543         *appData.gateway != NULLCHAR) {
1544         if (appData.debugMode) {
1545             fprintf(debugFP, ">ICS: ");
1546             show_bytes(debugFP, message, count);
1547             fprintf(debugFP, "\n");
1548         }
1549         return OutputToProcess(pr, message, count, outError);
1550     }
1551
1552     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1553     p = message;
1554     q = buf;
1555     left = count;
1556     newcount = 0;
1557     while (left) {
1558         if (q >= buflim) {
1559             if (appData.debugMode) {
1560                 fprintf(debugFP, ">ICS: ");
1561                 show_bytes(debugFP, buf, newcount);
1562                 fprintf(debugFP, "\n");
1563             }
1564             outcount = OutputToProcess(pr, buf, newcount, outError);
1565             if (outcount < newcount) return -1; /* to be sure */
1566             q = buf;
1567             newcount = 0;
1568         }
1569         if (*p == '\n') {
1570             *q++ = '\r';
1571             newcount++;
1572         } else if (((unsigned char) *p) == TN_IAC) {
1573             *q++ = (char) TN_IAC;
1574             newcount ++;
1575         }
1576         *q++ = *p++;
1577         newcount++;
1578         left--;
1579     }
1580     if (appData.debugMode) {
1581         fprintf(debugFP, ">ICS: ");
1582         show_bytes(debugFP, buf, newcount);
1583         fprintf(debugFP, "\n");
1584     }
1585     outcount = OutputToProcess(pr, buf, newcount, outError);
1586     if (outcount < newcount) return -1; /* to be sure */
1587     return count;
1588 }
1589
1590 void
1591 read_from_player(isr, closure, message, count, error)
1592      InputSourceRef isr;
1593      VOIDSTAR closure;
1594      char *message;
1595      int count;
1596      int error;
1597 {
1598     int outError, outCount;
1599     static int gotEof = 0;
1600
1601     /* Pass data read from player on to ICS */
1602     if (count > 0) {
1603         gotEof = 0;
1604         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1605         if (outCount < count) {
1606             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1607         }
1608     } else if (count < 0) {
1609         RemoveInputSource(isr);
1610         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1611     } else if (gotEof++ > 0) {
1612         RemoveInputSource(isr);
1613         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1614     }
1615 }
1616
1617 void
1618 KeepAlive()
1619 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1620     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1621     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1622     SendToICS("date\n");
1623     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1624 }
1625
1626 /* added routine for printf style output to ics */
1627 void ics_printf(char *format, ...)
1628 {
1629     char buffer[MSG_SIZ];
1630     va_list args;
1631
1632     va_start(args, format);
1633     vsnprintf(buffer, sizeof(buffer), format, args);
1634     buffer[sizeof(buffer)-1] = '\0';
1635     SendToICS(buffer);
1636     va_end(args);
1637 }
1638
1639 void
1640 SendToICS(s)
1641      char *s;
1642 {
1643     int count, outCount, outError;
1644
1645     if (icsPR == NULL) return;
1646
1647     count = strlen(s);
1648     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1649     if (outCount < count) {
1650         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1651     }
1652 }
1653
1654 /* This is used for sending logon scripts to the ICS. Sending
1655    without a delay causes problems when using timestamp on ICC
1656    (at least on my machine). */
1657 void
1658 SendToICSDelayed(s,msdelay)
1659      char *s;
1660      long msdelay;
1661 {
1662     int count, outCount, outError;
1663
1664     if (icsPR == NULL) return;
1665
1666     count = strlen(s);
1667     if (appData.debugMode) {
1668         fprintf(debugFP, ">ICS: ");
1669         show_bytes(debugFP, s, count);
1670         fprintf(debugFP, "\n");
1671     }
1672     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1673                                       msdelay);
1674     if (outCount < count) {
1675         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1676     }
1677 }
1678
1679
1680 /* Remove all highlighting escape sequences in s
1681    Also deletes any suffix starting with '('
1682    */
1683 char *
1684 StripHighlightAndTitle(s)
1685      char *s;
1686 {
1687     static char retbuf[MSG_SIZ];
1688     char *p = retbuf;
1689
1690     while (*s != NULLCHAR) {
1691         while (*s == '\033') {
1692             while (*s != NULLCHAR && !isalpha(*s)) s++;
1693             if (*s != NULLCHAR) s++;
1694         }
1695         while (*s != NULLCHAR && *s != '\033') {
1696             if (*s == '(' || *s == '[') {
1697                 *p = NULLCHAR;
1698                 return retbuf;
1699             }
1700             *p++ = *s++;
1701         }
1702     }
1703     *p = NULLCHAR;
1704     return retbuf;
1705 }
1706
1707 /* Remove all highlighting escape sequences in s */
1708 char *
1709 StripHighlight(s)
1710      char *s;
1711 {
1712     static char retbuf[MSG_SIZ];
1713     char *p = retbuf;
1714
1715     while (*s != NULLCHAR) {
1716         while (*s == '\033') {
1717             while (*s != NULLCHAR && !isalpha(*s)) s++;
1718             if (*s != NULLCHAR) s++;
1719         }
1720         while (*s != NULLCHAR && *s != '\033') {
1721             *p++ = *s++;
1722         }
1723     }
1724     *p = NULLCHAR;
1725     return retbuf;
1726 }
1727
1728 char *variantNames[] = VARIANT_NAMES;
1729 char *
1730 VariantName(v)
1731      VariantClass v;
1732 {
1733     return variantNames[v];
1734 }
1735
1736
1737 /* Identify a variant from the strings the chess servers use or the
1738    PGN Variant tag names we use. */
1739 VariantClass
1740 StringToVariant(e)
1741      char *e;
1742 {
1743     char *p;
1744     int wnum = -1;
1745     VariantClass v = VariantNormal;
1746     int i, found = FALSE;
1747     char buf[MSG_SIZ];
1748     int len;
1749
1750     if (!e) return v;
1751
1752     /* [HGM] skip over optional board-size prefixes */
1753     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1754         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1755         while( *e++ != '_');
1756     }
1757
1758     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1759         v = VariantNormal;
1760         found = TRUE;
1761     } else
1762     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1763       if (StrCaseStr(e, variantNames[i])) {
1764         v = (VariantClass) i;
1765         found = TRUE;
1766         break;
1767       }
1768     }
1769
1770     if (!found) {
1771       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1772           || StrCaseStr(e, "wild/fr")
1773           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1774         v = VariantFischeRandom;
1775       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1776                  (i = 1, p = StrCaseStr(e, "w"))) {
1777         p += i;
1778         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1779         if (isdigit(*p)) {
1780           wnum = atoi(p);
1781         } else {
1782           wnum = -1;
1783         }
1784         switch (wnum) {
1785         case 0: /* FICS only, actually */
1786         case 1:
1787           /* Castling legal even if K starts on d-file */
1788           v = VariantWildCastle;
1789           break;
1790         case 2:
1791         case 3:
1792         case 4:
1793           /* Castling illegal even if K & R happen to start in
1794              normal positions. */
1795           v = VariantNoCastle;
1796           break;
1797         case 5:
1798         case 7:
1799         case 8:
1800         case 10:
1801         case 11:
1802         case 12:
1803         case 13:
1804         case 14:
1805         case 15:
1806         case 18:
1807         case 19:
1808           /* Castling legal iff K & R start in normal positions */
1809           v = VariantNormal;
1810           break;
1811         case 6:
1812         case 20:
1813         case 21:
1814           /* Special wilds for position setup; unclear what to do here */
1815           v = VariantLoadable;
1816           break;
1817         case 9:
1818           /* Bizarre ICC game */
1819           v = VariantTwoKings;
1820           break;
1821         case 16:
1822           v = VariantKriegspiel;
1823           break;
1824         case 17:
1825           v = VariantLosers;
1826           break;
1827         case 22:
1828           v = VariantFischeRandom;
1829           break;
1830         case 23:
1831           v = VariantCrazyhouse;
1832           break;
1833         case 24:
1834           v = VariantBughouse;
1835           break;
1836         case 25:
1837           v = Variant3Check;
1838           break;
1839         case 26:
1840           /* Not quite the same as FICS suicide! */
1841           v = VariantGiveaway;
1842           break;
1843         case 27:
1844           v = VariantAtomic;
1845           break;
1846         case 28:
1847           v = VariantShatranj;
1848           break;
1849
1850         /* Temporary names for future ICC types.  The name *will* change in
1851            the next xboard/WinBoard release after ICC defines it. */
1852         case 29:
1853           v = Variant29;
1854           break;
1855         case 30:
1856           v = Variant30;
1857           break;
1858         case 31:
1859           v = Variant31;
1860           break;
1861         case 32:
1862           v = Variant32;
1863           break;
1864         case 33:
1865           v = Variant33;
1866           break;
1867         case 34:
1868           v = Variant34;
1869           break;
1870         case 35:
1871           v = Variant35;
1872           break;
1873         case 36:
1874           v = Variant36;
1875           break;
1876         case 37:
1877           v = VariantShogi;
1878           break;
1879         case 38:
1880           v = VariantXiangqi;
1881           break;
1882         case 39:
1883           v = VariantCourier;
1884           break;
1885         case 40:
1886           v = VariantGothic;
1887           break;
1888         case 41:
1889           v = VariantCapablanca;
1890           break;
1891         case 42:
1892           v = VariantKnightmate;
1893           break;
1894         case 43:
1895           v = VariantFairy;
1896           break;
1897         case 44:
1898           v = VariantCylinder;
1899           break;
1900         case 45:
1901           v = VariantFalcon;
1902           break;
1903         case 46:
1904           v = VariantCapaRandom;
1905           break;
1906         case 47:
1907           v = VariantBerolina;
1908           break;
1909         case 48:
1910           v = VariantJanus;
1911           break;
1912         case 49:
1913           v = VariantSuper;
1914           break;
1915         case 50:
1916           v = VariantGreat;
1917           break;
1918         case -1:
1919           /* Found "wild" or "w" in the string but no number;
1920              must assume it's normal chess. */
1921           v = VariantNormal;
1922           break;
1923         default:
1924           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1925           if( (len > MSG_SIZ) && appData.debugMode )
1926             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1927
1928           DisplayError(buf, 0);
1929           v = VariantUnknown;
1930           break;
1931         }
1932       }
1933     }
1934     if (appData.debugMode) {
1935       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1936               e, wnum, VariantName(v));
1937     }
1938     return v;
1939 }
1940
1941 static int leftover_start = 0, leftover_len = 0;
1942 char star_match[STAR_MATCH_N][MSG_SIZ];
1943
1944 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1945    advance *index beyond it, and set leftover_start to the new value of
1946    *index; else return FALSE.  If pattern contains the character '*', it
1947    matches any sequence of characters not containing '\r', '\n', or the
1948    character following the '*' (if any), and the matched sequence(s) are
1949    copied into star_match.
1950    */
1951 int
1952 looking_at(buf, index, pattern)
1953      char *buf;
1954      int *index;
1955      char *pattern;
1956 {
1957     char *bufp = &buf[*index], *patternp = pattern;
1958     int star_count = 0;
1959     char *matchp = star_match[0];
1960
1961     for (;;) {
1962         if (*patternp == NULLCHAR) {
1963             *index = leftover_start = bufp - buf;
1964             *matchp = NULLCHAR;
1965             return TRUE;
1966         }
1967         if (*bufp == NULLCHAR) return FALSE;
1968         if (*patternp == '*') {
1969             if (*bufp == *(patternp + 1)) {
1970                 *matchp = NULLCHAR;
1971                 matchp = star_match[++star_count];
1972                 patternp += 2;
1973                 bufp++;
1974                 continue;
1975             } else if (*bufp == '\n' || *bufp == '\r') {
1976                 patternp++;
1977                 if (*patternp == NULLCHAR)
1978                   continue;
1979                 else
1980                   return FALSE;
1981             } else {
1982                 *matchp++ = *bufp++;
1983                 continue;
1984             }
1985         }
1986         if (*patternp != *bufp) return FALSE;
1987         patternp++;
1988         bufp++;
1989     }
1990 }
1991
1992 void
1993 SendToPlayer(data, length)
1994      char *data;
1995      int length;
1996 {
1997     int error, outCount;
1998     outCount = OutputToProcess(NoProc, data, length, &error);
1999     if (outCount < length) {
2000         DisplayFatalError(_("Error writing to display"), error, 1);
2001     }
2002 }
2003
2004 void
2005 PackHolding(packed, holding)
2006      char packed[];
2007      char *holding;
2008 {
2009     char *p = holding;
2010     char *q = packed;
2011     int runlength = 0;
2012     int curr = 9999;
2013     do {
2014         if (*p == curr) {
2015             runlength++;
2016         } else {
2017             switch (runlength) {
2018               case 0:
2019                 break;
2020               case 1:
2021                 *q++ = curr;
2022                 break;
2023               case 2:
2024                 *q++ = curr;
2025                 *q++ = curr;
2026                 break;
2027               default:
2028                 sprintf(q, "%d", runlength);
2029                 while (*q) q++;
2030                 *q++ = curr;
2031                 break;
2032             }
2033             runlength = 1;
2034             curr = *p;
2035         }
2036     } while (*p++);
2037     *q = NULLCHAR;
2038 }
2039
2040 /* Telnet protocol requests from the front end */
2041 void
2042 TelnetRequest(ddww, option)
2043      unsigned char ddww, option;
2044 {
2045     unsigned char msg[3];
2046     int outCount, outError;
2047
2048     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2049
2050     if (appData.debugMode) {
2051         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2052         switch (ddww) {
2053           case TN_DO:
2054             ddwwStr = "DO";
2055             break;
2056           case TN_DONT:
2057             ddwwStr = "DONT";
2058             break;
2059           case TN_WILL:
2060             ddwwStr = "WILL";
2061             break;
2062           case TN_WONT:
2063             ddwwStr = "WONT";
2064             break;
2065           default:
2066             ddwwStr = buf1;
2067             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2068             break;
2069         }
2070         switch (option) {
2071           case TN_ECHO:
2072             optionStr = "ECHO";
2073             break;
2074           default:
2075             optionStr = buf2;
2076             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2077             break;
2078         }
2079         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2080     }
2081     msg[0] = TN_IAC;
2082     msg[1] = ddww;
2083     msg[2] = option;
2084     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2085     if (outCount < 3) {
2086         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2087     }
2088 }
2089
2090 void
2091 DoEcho()
2092 {
2093     if (!appData.icsActive) return;
2094     TelnetRequest(TN_DO, TN_ECHO);
2095 }
2096
2097 void
2098 DontEcho()
2099 {
2100     if (!appData.icsActive) return;
2101     TelnetRequest(TN_DONT, TN_ECHO);
2102 }
2103
2104 void
2105 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2106 {
2107     /* put the holdings sent to us by the server on the board holdings area */
2108     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2109     char p;
2110     ChessSquare piece;
2111
2112     if(gameInfo.holdingsWidth < 2)  return;
2113     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2114         return; // prevent overwriting by pre-board holdings
2115
2116     if( (int)lowestPiece >= BlackPawn ) {
2117         holdingsColumn = 0;
2118         countsColumn = 1;
2119         holdingsStartRow = BOARD_HEIGHT-1;
2120         direction = -1;
2121     } else {
2122         holdingsColumn = BOARD_WIDTH-1;
2123         countsColumn = BOARD_WIDTH-2;
2124         holdingsStartRow = 0;
2125         direction = 1;
2126     }
2127
2128     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2129         board[i][holdingsColumn] = EmptySquare;
2130         board[i][countsColumn]   = (ChessSquare) 0;
2131     }
2132     while( (p=*holdings++) != NULLCHAR ) {
2133         piece = CharToPiece( ToUpper(p) );
2134         if(piece == EmptySquare) continue;
2135         /*j = (int) piece - (int) WhitePawn;*/
2136         j = PieceToNumber(piece);
2137         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2138         if(j < 0) continue;               /* should not happen */
2139         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2140         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2141         board[holdingsStartRow+j*direction][countsColumn]++;
2142     }
2143 }
2144
2145
2146 void
2147 VariantSwitch(Board board, VariantClass newVariant)
2148 {
2149    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2150    static Board oldBoard;
2151
2152    startedFromPositionFile = FALSE;
2153    if(gameInfo.variant == newVariant) return;
2154
2155    /* [HGM] This routine is called each time an assignment is made to
2156     * gameInfo.variant during a game, to make sure the board sizes
2157     * are set to match the new variant. If that means adding or deleting
2158     * holdings, we shift the playing board accordingly
2159     * This kludge is needed because in ICS observe mode, we get boards
2160     * of an ongoing game without knowing the variant, and learn about the
2161     * latter only later. This can be because of the move list we requested,
2162     * in which case the game history is refilled from the beginning anyway,
2163     * but also when receiving holdings of a crazyhouse game. In the latter
2164     * case we want to add those holdings to the already received position.
2165     */
2166
2167
2168    if (appData.debugMode) {
2169      fprintf(debugFP, "Switch board from %s to %s\n",
2170              VariantName(gameInfo.variant), VariantName(newVariant));
2171      setbuf(debugFP, NULL);
2172    }
2173    shuffleOpenings = 0;       /* [HGM] shuffle */
2174    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2175    switch(newVariant)
2176      {
2177      case VariantShogi:
2178        newWidth = 9;  newHeight = 9;
2179        gameInfo.holdingsSize = 7;
2180      case VariantBughouse:
2181      case VariantCrazyhouse:
2182        newHoldingsWidth = 2; break;
2183      case VariantGreat:
2184        newWidth = 10;
2185      case VariantSuper:
2186        newHoldingsWidth = 2;
2187        gameInfo.holdingsSize = 8;
2188        break;
2189      case VariantGothic:
2190      case VariantCapablanca:
2191      case VariantCapaRandom:
2192        newWidth = 10;
2193      default:
2194        newHoldingsWidth = gameInfo.holdingsSize = 0;
2195      };
2196
2197    if(newWidth  != gameInfo.boardWidth  ||
2198       newHeight != gameInfo.boardHeight ||
2199       newHoldingsWidth != gameInfo.holdingsWidth ) {
2200
2201      /* shift position to new playing area, if needed */
2202      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2203        for(i=0; i<BOARD_HEIGHT; i++)
2204          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2205            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2206              board[i][j];
2207        for(i=0; i<newHeight; i++) {
2208          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2209          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2210        }
2211      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2212        for(i=0; i<BOARD_HEIGHT; i++)
2213          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2214            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2215              board[i][j];
2216      }
2217      gameInfo.boardWidth  = newWidth;
2218      gameInfo.boardHeight = newHeight;
2219      gameInfo.holdingsWidth = newHoldingsWidth;
2220      gameInfo.variant = newVariant;
2221      InitDrawingSizes(-2, 0);
2222    } else gameInfo.variant = newVariant;
2223    CopyBoard(oldBoard, board);   // remember correctly formatted board
2224      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2225    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2226 }
2227
2228 static int loggedOn = FALSE;
2229
2230 /*-- Game start info cache: --*/
2231 int gs_gamenum;
2232 char gs_kind[MSG_SIZ];
2233 static char player1Name[128] = "";
2234 static char player2Name[128] = "";
2235 static char cont_seq[] = "\n\\   ";
2236 static int player1Rating = -1;
2237 static int player2Rating = -1;
2238 /*----------------------------*/
2239
2240 ColorClass curColor = ColorNormal;
2241 int suppressKibitz = 0;
2242
2243 // [HGM] seekgraph
2244 Boolean soughtPending = FALSE;
2245 Boolean seekGraphUp;
2246 #define MAX_SEEK_ADS 200
2247 #define SQUARE 0x80
2248 char *seekAdList[MAX_SEEK_ADS];
2249 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2250 float tcList[MAX_SEEK_ADS];
2251 char colorList[MAX_SEEK_ADS];
2252 int nrOfSeekAds = 0;
2253 int minRating = 1010, maxRating = 2800;
2254 int hMargin = 10, vMargin = 20, h, w;
2255 extern int squareSize, lineGap;
2256
2257 void
2258 PlotSeekAd(int i)
2259 {
2260         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2261         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2262         if(r < minRating+100 && r >=0 ) r = minRating+100;
2263         if(r > maxRating) r = maxRating;
2264         if(tc < 1.) tc = 1.;
2265         if(tc > 95.) tc = 95.;
2266         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2267         y = ((double)r - minRating)/(maxRating - minRating)
2268             * (h-vMargin-squareSize/8-1) + vMargin;
2269         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2270         if(strstr(seekAdList[i], " u ")) color = 1;
2271         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2272            !strstr(seekAdList[i], "bullet") &&
2273            !strstr(seekAdList[i], "blitz") &&
2274            !strstr(seekAdList[i], "standard") ) color = 2;
2275         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2276         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2277 }
2278
2279 void
2280 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2281 {
2282         char buf[MSG_SIZ], *ext = "";
2283         VariantClass v = StringToVariant(type);
2284         if(strstr(type, "wild")) {
2285             ext = type + 4; // append wild number
2286             if(v == VariantFischeRandom) type = "chess960"; else
2287             if(v == VariantLoadable) type = "setup"; else
2288             type = VariantName(v);
2289         }
2290         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2291         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2292             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2293             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2294             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2295             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2296             seekNrList[nrOfSeekAds] = nr;
2297             zList[nrOfSeekAds] = 0;
2298             seekAdList[nrOfSeekAds++] = StrSave(buf);
2299             if(plot) PlotSeekAd(nrOfSeekAds-1);
2300         }
2301 }
2302
2303 void
2304 EraseSeekDot(int i)
2305 {
2306     int x = xList[i], y = yList[i], d=squareSize/4, k;
2307     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2308     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2309     // now replot every dot that overlapped
2310     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2311         int xx = xList[k], yy = yList[k];
2312         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2313             DrawSeekDot(xx, yy, colorList[k]);
2314     }
2315 }
2316
2317 void
2318 RemoveSeekAd(int nr)
2319 {
2320         int i;
2321         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2322             EraseSeekDot(i);
2323             if(seekAdList[i]) free(seekAdList[i]);
2324             seekAdList[i] = seekAdList[--nrOfSeekAds];
2325             seekNrList[i] = seekNrList[nrOfSeekAds];
2326             ratingList[i] = ratingList[nrOfSeekAds];
2327             colorList[i]  = colorList[nrOfSeekAds];
2328             tcList[i] = tcList[nrOfSeekAds];
2329             xList[i]  = xList[nrOfSeekAds];
2330             yList[i]  = yList[nrOfSeekAds];
2331             zList[i]  = zList[nrOfSeekAds];
2332             seekAdList[nrOfSeekAds] = NULL;
2333             break;
2334         }
2335 }
2336
2337 Boolean
2338 MatchSoughtLine(char *line)
2339 {
2340     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2341     int nr, base, inc, u=0; char dummy;
2342
2343     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2344        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2345        (u=1) &&
2346        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2347         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2348         // match: compact and save the line
2349         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2350         return TRUE;
2351     }
2352     return FALSE;
2353 }
2354
2355 int
2356 DrawSeekGraph()
2357 {
2358     int i;
2359     if(!seekGraphUp) return FALSE;
2360     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2361     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2362
2363     DrawSeekBackground(0, 0, w, h);
2364     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2365     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2366     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2367         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2368         yy = h-1-yy;
2369         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2370         if(i%500 == 0) {
2371             char buf[MSG_SIZ];
2372             snprintf(buf, MSG_SIZ, "%d", i);
2373             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2374         }
2375     }
2376     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2377     for(i=1; i<100; i+=(i<10?1:5)) {
2378         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2379         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2380         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2381             char buf[MSG_SIZ];
2382             snprintf(buf, MSG_SIZ, "%d", i);
2383             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2384         }
2385     }
2386     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2387     return TRUE;
2388 }
2389
2390 int SeekGraphClick(ClickType click, int x, int y, int moving)
2391 {
2392     static int lastDown = 0, displayed = 0, lastSecond;
2393     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2394         if(click == Release || moving) return FALSE;
2395         nrOfSeekAds = 0;
2396         soughtPending = TRUE;
2397         SendToICS(ics_prefix);
2398         SendToICS("sought\n"); // should this be "sought all"?
2399     } else { // issue challenge based on clicked ad
2400         int dist = 10000; int i, closest = 0, second = 0;
2401         for(i=0; i<nrOfSeekAds; i++) {
2402             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2403             if(d < dist) { dist = d; closest = i; }
2404             second += (d - zList[i] < 120); // count in-range ads
2405             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2406         }
2407         if(dist < 120) {
2408             char buf[MSG_SIZ];
2409             second = (second > 1);
2410             if(displayed != closest || second != lastSecond) {
2411                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2412                 lastSecond = second; displayed = closest;
2413             }
2414             if(click == Press) {
2415                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2416                 lastDown = closest;
2417                 return TRUE;
2418             } // on press 'hit', only show info
2419             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2420             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2421             SendToICS(ics_prefix);
2422             SendToICS(buf);
2423             return TRUE; // let incoming board of started game pop down the graph
2424         } else if(click == Release) { // release 'miss' is ignored
2425             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2426             if(moving == 2) { // right up-click
2427                 nrOfSeekAds = 0; // refresh graph
2428                 soughtPending = TRUE;
2429                 SendToICS(ics_prefix);
2430                 SendToICS("sought\n"); // should this be "sought all"?
2431             }
2432             return TRUE;
2433         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2434         // press miss or release hit 'pop down' seek graph
2435         seekGraphUp = FALSE;
2436         DrawPosition(TRUE, NULL);
2437     }
2438     return TRUE;
2439 }
2440
2441 void
2442 read_from_ics(isr, closure, data, count, error)
2443      InputSourceRef isr;
2444      VOIDSTAR closure;
2445      char *data;
2446      int count;
2447      int error;
2448 {
2449 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2450 #define STARTED_NONE 0
2451 #define STARTED_MOVES 1
2452 #define STARTED_BOARD 2
2453 #define STARTED_OBSERVE 3
2454 #define STARTED_HOLDINGS 4
2455 #define STARTED_CHATTER 5
2456 #define STARTED_COMMENT 6
2457 #define STARTED_MOVES_NOHIDE 7
2458
2459     static int started = STARTED_NONE;
2460     static char parse[20000];
2461     static int parse_pos = 0;
2462     static char buf[BUF_SIZE + 1];
2463     static int firstTime = TRUE, intfSet = FALSE;
2464     static ColorClass prevColor = ColorNormal;
2465     static int savingComment = FALSE;
2466     static int cmatch = 0; // continuation sequence match
2467     char *bp;
2468     char str[MSG_SIZ];
2469     int i, oldi;
2470     int buf_len;
2471     int next_out;
2472     int tkind;
2473     int backup;    /* [DM] For zippy color lines */
2474     char *p;
2475     char talker[MSG_SIZ]; // [HGM] chat
2476     int channel;
2477
2478     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2479
2480     if (appData.debugMode) {
2481       if (!error) {
2482         fprintf(debugFP, "<ICS: ");
2483         show_bytes(debugFP, data, count);
2484         fprintf(debugFP, "\n");
2485       }
2486     }
2487
2488     if (appData.debugMode) { int f = forwardMostMove;
2489         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2490                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2491                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2492     }
2493     if (count > 0) {
2494         /* If last read ended with a partial line that we couldn't parse,
2495            prepend it to the new read and try again. */
2496         if (leftover_len > 0) {
2497             for (i=0; i<leftover_len; i++)
2498               buf[i] = buf[leftover_start + i];
2499         }
2500
2501     /* copy new characters into the buffer */
2502     bp = buf + leftover_len;
2503     buf_len=leftover_len;
2504     for (i=0; i<count; i++)
2505     {
2506         // ignore these
2507         if (data[i] == '\r')
2508             continue;
2509
2510         // join lines split by ICS?
2511         if (!appData.noJoin)
2512         {
2513             /*
2514                 Joining just consists of finding matches against the
2515                 continuation sequence, and discarding that sequence
2516                 if found instead of copying it.  So, until a match
2517                 fails, there's nothing to do since it might be the
2518                 complete sequence, and thus, something we don't want
2519                 copied.
2520             */
2521             if (data[i] == cont_seq[cmatch])
2522             {
2523                 cmatch++;
2524                 if (cmatch == strlen(cont_seq))
2525                 {
2526                     cmatch = 0; // complete match.  just reset the counter
2527
2528                     /*
2529                         it's possible for the ICS to not include the space
2530                         at the end of the last word, making our [correct]
2531                         join operation fuse two separate words.  the server
2532                         does this when the space occurs at the width setting.
2533                     */
2534                     if (!buf_len || buf[buf_len-1] != ' ')
2535                     {
2536                         *bp++ = ' ';
2537                         buf_len++;
2538                     }
2539                 }
2540                 continue;
2541             }
2542             else if (cmatch)
2543             {
2544                 /*
2545                     match failed, so we have to copy what matched before
2546                     falling through and copying this character.  In reality,
2547                     this will only ever be just the newline character, but
2548                     it doesn't hurt to be precise.
2549                 */
2550                 strncpy(bp, cont_seq, cmatch);
2551                 bp += cmatch;
2552                 buf_len += cmatch;
2553                 cmatch = 0;
2554             }
2555         }
2556
2557         // copy this char
2558         *bp++ = data[i];
2559         buf_len++;
2560     }
2561
2562         buf[buf_len] = NULLCHAR;
2563 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2564         next_out = 0;
2565         leftover_start = 0;
2566
2567         i = 0;
2568         while (i < buf_len) {
2569             /* Deal with part of the TELNET option negotiation
2570                protocol.  We refuse to do anything beyond the
2571                defaults, except that we allow the WILL ECHO option,
2572                which ICS uses to turn off password echoing when we are
2573                directly connected to it.  We reject this option
2574                if localLineEditing mode is on (always on in xboard)
2575                and we are talking to port 23, which might be a real
2576                telnet server that will try to keep WILL ECHO on permanently.
2577              */
2578             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2579                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2580                 unsigned char option;
2581                 oldi = i;
2582                 switch ((unsigned char) buf[++i]) {
2583                   case TN_WILL:
2584                     if (appData.debugMode)
2585                       fprintf(debugFP, "\n<WILL ");
2586                     switch (option = (unsigned char) buf[++i]) {
2587                       case TN_ECHO:
2588                         if (appData.debugMode)
2589                           fprintf(debugFP, "ECHO ");
2590                         /* Reply only if this is a change, according
2591                            to the protocol rules. */
2592                         if (remoteEchoOption) break;
2593                         if (appData.localLineEditing &&
2594                             atoi(appData.icsPort) == TN_PORT) {
2595                             TelnetRequest(TN_DONT, TN_ECHO);
2596                         } else {
2597                             EchoOff();
2598                             TelnetRequest(TN_DO, TN_ECHO);
2599                             remoteEchoOption = TRUE;
2600                         }
2601                         break;
2602                       default:
2603                         if (appData.debugMode)
2604                           fprintf(debugFP, "%d ", option);
2605                         /* Whatever this is, we don't want it. */
2606                         TelnetRequest(TN_DONT, option);
2607                         break;
2608                     }
2609                     break;
2610                   case TN_WONT:
2611                     if (appData.debugMode)
2612                       fprintf(debugFP, "\n<WONT ");
2613                     switch (option = (unsigned char) buf[++i]) {
2614                       case TN_ECHO:
2615                         if (appData.debugMode)
2616                           fprintf(debugFP, "ECHO ");
2617                         /* Reply only if this is a change, according
2618                            to the protocol rules. */
2619                         if (!remoteEchoOption) break;
2620                         EchoOn();
2621                         TelnetRequest(TN_DONT, TN_ECHO);
2622                         remoteEchoOption = FALSE;
2623                         break;
2624                       default:
2625                         if (appData.debugMode)
2626                           fprintf(debugFP, "%d ", (unsigned char) option);
2627                         /* Whatever this is, it must already be turned
2628                            off, because we never agree to turn on
2629                            anything non-default, so according to the
2630                            protocol rules, we don't reply. */
2631                         break;
2632                     }
2633                     break;
2634                   case TN_DO:
2635                     if (appData.debugMode)
2636                       fprintf(debugFP, "\n<DO ");
2637                     switch (option = (unsigned char) buf[++i]) {
2638                       default:
2639                         /* Whatever this is, we refuse to do it. */
2640                         if (appData.debugMode)
2641                           fprintf(debugFP, "%d ", option);
2642                         TelnetRequest(TN_WONT, option);
2643                         break;
2644                     }
2645                     break;
2646                   case TN_DONT:
2647                     if (appData.debugMode)
2648                       fprintf(debugFP, "\n<DONT ");
2649                     switch (option = (unsigned char) buf[++i]) {
2650                       default:
2651                         if (appData.debugMode)
2652                           fprintf(debugFP, "%d ", option);
2653                         /* Whatever this is, we are already not doing
2654                            it, because we never agree to do anything
2655                            non-default, so according to the protocol
2656                            rules, we don't reply. */
2657                         break;
2658                     }
2659                     break;
2660                   case TN_IAC:
2661                     if (appData.debugMode)
2662                       fprintf(debugFP, "\n<IAC ");
2663                     /* Doubled IAC; pass it through */
2664                     i--;
2665                     break;
2666                   default:
2667                     if (appData.debugMode)
2668                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2669                     /* Drop all other telnet commands on the floor */
2670                     break;
2671                 }
2672                 if (oldi > next_out)
2673                   SendToPlayer(&buf[next_out], oldi - next_out);
2674                 if (++i > next_out)
2675                   next_out = i;
2676                 continue;
2677             }
2678
2679             /* OK, this at least will *usually* work */
2680             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2681                 loggedOn = TRUE;
2682             }
2683
2684             if (loggedOn && !intfSet) {
2685                 if (ics_type == ICS_ICC) {
2686                   snprintf(str, MSG_SIZ,
2687                           "/set-quietly interface %s\n/set-quietly style 12\n",
2688                           programVersion);
2689                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2690                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2691                 } else if (ics_type == ICS_CHESSNET) {
2692                   snprintf(str, MSG_SIZ, "/style 12\n");
2693                 } else {
2694                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2695                   strcat(str, programVersion);
2696                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2697                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2698                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2699 #ifdef WIN32
2700                   strcat(str, "$iset nohighlight 1\n");
2701 #endif
2702                   strcat(str, "$iset lock 1\n$style 12\n");
2703                 }
2704                 SendToICS(str);
2705                 NotifyFrontendLogin();
2706                 intfSet = TRUE;
2707             }
2708
2709             if (started == STARTED_COMMENT) {
2710                 /* Accumulate characters in comment */
2711                 parse[parse_pos++] = buf[i];
2712                 if (buf[i] == '\n') {
2713                     parse[parse_pos] = NULLCHAR;
2714                     if(chattingPartner>=0) {
2715                         char mess[MSG_SIZ];
2716                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2717                         OutputChatMessage(chattingPartner, mess);
2718                         chattingPartner = -1;
2719                         next_out = i+1; // [HGM] suppress printing in ICS window
2720                     } else
2721                     if(!suppressKibitz) // [HGM] kibitz
2722                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2723                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2724                         int nrDigit = 0, nrAlph = 0, j;
2725                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2726                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2727                         parse[parse_pos] = NULLCHAR;
2728                         // try to be smart: if it does not look like search info, it should go to
2729                         // ICS interaction window after all, not to engine-output window.
2730                         for(j=0; j<parse_pos; j++) { // count letters and digits
2731                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2732                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2733                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2734                         }
2735                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2736                             int depth=0; float score;
2737                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2738                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2739                                 pvInfoList[forwardMostMove-1].depth = depth;
2740                                 pvInfoList[forwardMostMove-1].score = 100*score;
2741                             }
2742                             OutputKibitz(suppressKibitz, parse);
2743                         } else {
2744                             char tmp[MSG_SIZ];
2745                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2746                             SendToPlayer(tmp, strlen(tmp));
2747                         }
2748                         next_out = i+1; // [HGM] suppress printing in ICS window
2749                     }
2750                     started = STARTED_NONE;
2751                 } else {
2752                     /* Don't match patterns against characters in comment */
2753                     i++;
2754                     continue;
2755                 }
2756             }
2757             if (started == STARTED_CHATTER) {
2758                 if (buf[i] != '\n') {
2759                     /* Don't match patterns against characters in chatter */
2760                     i++;
2761                     continue;
2762                 }
2763                 started = STARTED_NONE;
2764                 if(suppressKibitz) next_out = i+1;
2765             }
2766
2767             /* Kludge to deal with rcmd protocol */
2768             if (firstTime && looking_at(buf, &i, "\001*")) {
2769                 DisplayFatalError(&buf[1], 0, 1);
2770                 continue;
2771             } else {
2772                 firstTime = FALSE;
2773             }
2774
2775             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2776                 ics_type = ICS_ICC;
2777                 ics_prefix = "/";
2778                 if (appData.debugMode)
2779                   fprintf(debugFP, "ics_type %d\n", ics_type);
2780                 continue;
2781             }
2782             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2783                 ics_type = ICS_FICS;
2784                 ics_prefix = "$";
2785                 if (appData.debugMode)
2786                   fprintf(debugFP, "ics_type %d\n", ics_type);
2787                 continue;
2788             }
2789             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2790                 ics_type = ICS_CHESSNET;
2791                 ics_prefix = "/";
2792                 if (appData.debugMode)
2793                   fprintf(debugFP, "ics_type %d\n", ics_type);
2794                 continue;
2795             }
2796
2797             if (!loggedOn &&
2798                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2799                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2800                  looking_at(buf, &i, "will be \"*\""))) {
2801               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2802               continue;
2803             }
2804
2805             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2806               char buf[MSG_SIZ];
2807               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2808               DisplayIcsInteractionTitle(buf);
2809               have_set_title = TRUE;
2810             }
2811
2812             /* skip finger notes */
2813             if (started == STARTED_NONE &&
2814                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2815                  (buf[i] == '1' && buf[i+1] == '0')) &&
2816                 buf[i+2] == ':' && buf[i+3] == ' ') {
2817               started = STARTED_CHATTER;
2818               i += 3;
2819               continue;
2820             }
2821
2822             oldi = i;
2823             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2824             if(appData.seekGraph) {
2825                 if(soughtPending && MatchSoughtLine(buf+i)) {
2826                     i = strstr(buf+i, "rated") - buf;
2827                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2828                     next_out = leftover_start = i;
2829                     started = STARTED_CHATTER;
2830                     suppressKibitz = TRUE;
2831                     continue;
2832                 }
2833                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2834                         && looking_at(buf, &i, "* ads displayed")) {
2835                     soughtPending = FALSE;
2836                     seekGraphUp = TRUE;
2837                     DrawSeekGraph();
2838                     continue;
2839                 }
2840                 if(appData.autoRefresh) {
2841                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2842                         int s = (ics_type == ICS_ICC); // ICC format differs
2843                         if(seekGraphUp)
2844                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2845                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2846                         looking_at(buf, &i, "*% "); // eat prompt
2847                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2848                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2849                         next_out = i; // suppress
2850                         continue;
2851                     }
2852                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2853                         char *p = star_match[0];
2854                         while(*p) {
2855                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2856                             while(*p && *p++ != ' '); // next
2857                         }
2858                         looking_at(buf, &i, "*% "); // eat prompt
2859                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2860                         next_out = i;
2861                         continue;
2862                     }
2863                 }
2864             }
2865
2866             /* skip formula vars */
2867             if (started == STARTED_NONE &&
2868                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2869               started = STARTED_CHATTER;
2870               i += 3;
2871               continue;
2872             }
2873
2874             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2875             if (appData.autoKibitz && started == STARTED_NONE &&
2876                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2877                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2878                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2879                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2880                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2881                         suppressKibitz = TRUE;
2882                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2883                         next_out = i;
2884                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2885                                 && (gameMode == IcsPlayingWhite)) ||
2886                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2887                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2888                             started = STARTED_CHATTER; // own kibitz we simply discard
2889                         else {
2890                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2891                             parse_pos = 0; parse[0] = NULLCHAR;
2892                             savingComment = TRUE;
2893                             suppressKibitz = gameMode != IcsObserving ? 2 :
2894                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2895                         }
2896                         continue;
2897                 } else
2898                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2899                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2900                          && atoi(star_match[0])) {
2901                     // suppress the acknowledgements of our own autoKibitz
2902                     char *p;
2903                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2904                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2905                     SendToPlayer(star_match[0], strlen(star_match[0]));
2906                     if(looking_at(buf, &i, "*% ")) // eat prompt
2907                         suppressKibitz = FALSE;
2908                     next_out = i;
2909                     continue;
2910                 }
2911             } // [HGM] kibitz: end of patch
2912
2913             // [HGM] chat: intercept tells by users for which we have an open chat window
2914             channel = -1;
2915             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2916                                            looking_at(buf, &i, "* whispers:") ||
2917                                            looking_at(buf, &i, "* kibitzes:") ||
2918                                            looking_at(buf, &i, "* shouts:") ||
2919                                            looking_at(buf, &i, "* c-shouts:") ||
2920                                            looking_at(buf, &i, "--> * ") ||
2921                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2922                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2923                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2924                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2925                 int p;
2926                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2927                 chattingPartner = -1;
2928
2929                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2930                 for(p=0; p<MAX_CHAT; p++) {
2931                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2932                     talker[0] = '['; strcat(talker, "] ");
2933                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2934                     chattingPartner = p; break;
2935                     }
2936                 } else
2937                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2938                 for(p=0; p<MAX_CHAT; p++) {
2939                     if(!strcmp("kibitzes", chatPartner[p])) {
2940                         talker[0] = '['; strcat(talker, "] ");
2941                         chattingPartner = p; break;
2942                     }
2943                 } else
2944                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2945                 for(p=0; p<MAX_CHAT; p++) {
2946                     if(!strcmp("whispers", chatPartner[p])) {
2947                         talker[0] = '['; strcat(talker, "] ");
2948                         chattingPartner = p; break;
2949                     }
2950                 } else
2951                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2952                   if(buf[i-8] == '-' && buf[i-3] == 't')
2953                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2954                     if(!strcmp("c-shouts", chatPartner[p])) {
2955                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2956                         chattingPartner = p; break;
2957                     }
2958                   }
2959                   if(chattingPartner < 0)
2960                   for(p=0; p<MAX_CHAT; p++) {
2961                     if(!strcmp("shouts", chatPartner[p])) {
2962                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2963                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2964                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2965                         chattingPartner = p; break;
2966                     }
2967                   }
2968                 }
2969                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2970                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2971                     talker[0] = 0; Colorize(ColorTell, FALSE);
2972                     chattingPartner = p; break;
2973                 }
2974                 if(chattingPartner<0) i = oldi; else {
2975                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2976                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2977                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978                     started = STARTED_COMMENT;
2979                     parse_pos = 0; parse[0] = NULLCHAR;
2980                     savingComment = 3 + chattingPartner; // counts as TRUE
2981                     suppressKibitz = TRUE;
2982                     continue;
2983                 }
2984             } // [HGM] chat: end of patch
2985
2986             if (appData.zippyTalk || appData.zippyPlay) {
2987                 /* [DM] Backup address for color zippy lines */
2988                 backup = i;
2989 #if ZIPPY
2990                if (loggedOn == TRUE)
2991                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2992                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2993 #endif
2994             } // [DM] 'else { ' deleted
2995                 if (
2996                     /* Regular tells and says */
2997                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2998                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2999                     looking_at(buf, &i, "* says: ") ||
3000                     /* Don't color "message" or "messages" output */
3001                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3002                     looking_at(buf, &i, "*. * at *:*: ") ||
3003                     looking_at(buf, &i, "--* (*:*): ") ||
3004                     /* Message notifications (same color as tells) */
3005                     looking_at(buf, &i, "* has left a message ") ||
3006                     looking_at(buf, &i, "* just sent you a message:\n") ||
3007                     /* Whispers and kibitzes */
3008                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3009                     looking_at(buf, &i, "* kibitzes: ") ||
3010                     /* Channel tells */
3011                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3012
3013                   if (tkind == 1 && strchr(star_match[0], ':')) {
3014                       /* Avoid "tells you:" spoofs in channels */
3015                      tkind = 3;
3016                   }
3017                   if (star_match[0][0] == NULLCHAR ||
3018                       strchr(star_match[0], ' ') ||
3019                       (tkind == 3 && strchr(star_match[1], ' '))) {
3020                     /* Reject bogus matches */
3021                     i = oldi;
3022                   } else {
3023                     if (appData.colorize) {
3024                       if (oldi > next_out) {
3025                         SendToPlayer(&buf[next_out], oldi - next_out);
3026                         next_out = oldi;
3027                       }
3028                       switch (tkind) {
3029                       case 1:
3030                         Colorize(ColorTell, FALSE);
3031                         curColor = ColorTell;
3032                         break;
3033                       case 2:
3034                         Colorize(ColorKibitz, FALSE);
3035                         curColor = ColorKibitz;
3036                         break;
3037                       case 3:
3038                         p = strrchr(star_match[1], '(');
3039                         if (p == NULL) {
3040                           p = star_match[1];
3041                         } else {
3042                           p++;
3043                         }
3044                         if (atoi(p) == 1) {
3045                           Colorize(ColorChannel1, FALSE);
3046                           curColor = ColorChannel1;
3047                         } else {
3048                           Colorize(ColorChannel, FALSE);
3049                           curColor = ColorChannel;
3050                         }
3051                         break;
3052                       case 5:
3053                         curColor = ColorNormal;
3054                         break;
3055                       }
3056                     }
3057                     if (started == STARTED_NONE && appData.autoComment &&
3058                         (gameMode == IcsObserving ||
3059                          gameMode == IcsPlayingWhite ||
3060                          gameMode == IcsPlayingBlack)) {
3061                       parse_pos = i - oldi;
3062                       memcpy(parse, &buf[oldi], parse_pos);
3063                       parse[parse_pos] = NULLCHAR;
3064                       started = STARTED_COMMENT;
3065                       savingComment = TRUE;
3066                     } else {
3067                       started = STARTED_CHATTER;
3068                       savingComment = FALSE;
3069                     }
3070                     loggedOn = TRUE;
3071                     continue;
3072                   }
3073                 }
3074
3075                 if (looking_at(buf, &i, "* s-shouts: ") ||
3076                     looking_at(buf, &i, "* c-shouts: ")) {
3077                     if (appData.colorize) {
3078                         if (oldi > next_out) {
3079                             SendToPlayer(&buf[next_out], oldi - next_out);
3080                             next_out = oldi;
3081                         }
3082                         Colorize(ColorSShout, FALSE);
3083                         curColor = ColorSShout;
3084                     }
3085                     loggedOn = TRUE;
3086                     started = STARTED_CHATTER;
3087                     continue;
3088                 }
3089
3090                 if (looking_at(buf, &i, "--->")) {
3091                     loggedOn = TRUE;
3092                     continue;
3093                 }
3094
3095                 if (looking_at(buf, &i, "* shouts: ") ||
3096                     looking_at(buf, &i, "--> ")) {
3097                     if (appData.colorize) {
3098                         if (oldi > next_out) {
3099                             SendToPlayer(&buf[next_out], oldi - next_out);
3100                             next_out = oldi;
3101                         }
3102                         Colorize(ColorShout, FALSE);
3103                         curColor = ColorShout;
3104                     }
3105                     loggedOn = TRUE;
3106                     started = STARTED_CHATTER;
3107                     continue;
3108                 }
3109
3110                 if (looking_at( buf, &i, "Challenge:")) {
3111                     if (appData.colorize) {
3112                         if (oldi > next_out) {
3113                             SendToPlayer(&buf[next_out], oldi - next_out);
3114                             next_out = oldi;
3115                         }
3116                         Colorize(ColorChallenge, FALSE);
3117                         curColor = ColorChallenge;
3118                     }
3119                     loggedOn = TRUE;
3120                     continue;
3121                 }
3122
3123                 if (looking_at(buf, &i, "* offers you") ||
3124                     looking_at(buf, &i, "* offers to be") ||
3125                     looking_at(buf, &i, "* would like to") ||
3126                     looking_at(buf, &i, "* requests to") ||
3127                     looking_at(buf, &i, "Your opponent offers") ||
3128                     looking_at(buf, &i, "Your opponent requests")) {
3129
3130                     if (appData.colorize) {
3131                         if (oldi > next_out) {
3132                             SendToPlayer(&buf[next_out], oldi - next_out);
3133                             next_out = oldi;
3134                         }
3135                         Colorize(ColorRequest, FALSE);
3136                         curColor = ColorRequest;
3137                     }
3138                     continue;
3139                 }
3140
3141                 if (looking_at(buf, &i, "* (*) seeking")) {
3142                     if (appData.colorize) {
3143                         if (oldi > next_out) {
3144                             SendToPlayer(&buf[next_out], oldi - next_out);
3145                             next_out = oldi;
3146                         }
3147                         Colorize(ColorSeek, FALSE);
3148                         curColor = ColorSeek;
3149                     }
3150                     continue;
3151             }
3152
3153             if (looking_at(buf, &i, "\\   ")) {
3154                 if (prevColor != ColorNormal) {
3155                     if (oldi > next_out) {
3156                         SendToPlayer(&buf[next_out], oldi - next_out);
3157                         next_out = oldi;
3158                     }
3159                     Colorize(prevColor, TRUE);
3160                     curColor = prevColor;
3161                 }
3162                 if (savingComment) {
3163                     parse_pos = i - oldi;
3164                     memcpy(parse, &buf[oldi], parse_pos);
3165                     parse[parse_pos] = NULLCHAR;
3166                     started = STARTED_COMMENT;
3167                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3168                         chattingPartner = savingComment - 3; // kludge to remember the box
3169                 } else {
3170                     started = STARTED_CHATTER;
3171                 }
3172                 continue;
3173             }
3174
3175             if (looking_at(buf, &i, "Black Strength :") ||
3176                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3177                 looking_at(buf, &i, "<10>") ||
3178                 looking_at(buf, &i, "#@#")) {
3179                 /* Wrong board style */
3180                 loggedOn = TRUE;
3181                 SendToICS(ics_prefix);
3182                 SendToICS("set style 12\n");
3183                 SendToICS(ics_prefix);
3184                 SendToICS("refresh\n");
3185                 continue;
3186             }
3187
3188             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3189                 ICSInitScript();
3190                 have_sent_ICS_logon = 1;
3191                 continue;
3192             }
3193
3194             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3195                 (looking_at(buf, &i, "\n<12> ") ||
3196                  looking_at(buf, &i, "<12> "))) {
3197                 loggedOn = TRUE;
3198                 if (oldi > next_out) {
3199                     SendToPlayer(&buf[next_out], oldi - next_out);
3200                 }
3201                 next_out = i;
3202                 started = STARTED_BOARD;
3203                 parse_pos = 0;
3204                 continue;
3205             }
3206
3207             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3208                 looking_at(buf, &i, "<b1> ")) {
3209                 if (oldi > next_out) {
3210                     SendToPlayer(&buf[next_out], oldi - next_out);
3211                 }
3212                 next_out = i;
3213                 started = STARTED_HOLDINGS;
3214                 parse_pos = 0;
3215                 continue;
3216             }
3217
3218             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3219                 loggedOn = TRUE;
3220                 /* Header for a move list -- first line */
3221
3222                 switch (ics_getting_history) {
3223                   case H_FALSE:
3224                     switch (gameMode) {
3225                       case IcsIdle:
3226                       case BeginningOfGame:
3227                         /* User typed "moves" or "oldmoves" while we
3228                            were idle.  Pretend we asked for these
3229                            moves and soak them up so user can step
3230                            through them and/or save them.
3231                            */
3232                         Reset(FALSE, TRUE);
3233                         gameMode = IcsObserving;
3234                         ModeHighlight();
3235                         ics_gamenum = -1;
3236                         ics_getting_history = H_GOT_UNREQ_HEADER;
3237                         break;
3238                       case EditGame: /*?*/
3239                       case EditPosition: /*?*/
3240                         /* Should above feature work in these modes too? */
3241                         /* For now it doesn't */
3242                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3243                         break;
3244                       default:
3245                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3246                         break;
3247                     }
3248                     break;
3249                   case H_REQUESTED:
3250                     /* Is this the right one? */
3251                     if (gameInfo.white && gameInfo.black &&
3252                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3253                         strcmp(gameInfo.black, star_match[2]) == 0) {
3254                         /* All is well */
3255                         ics_getting_history = H_GOT_REQ_HEADER;
3256                     }
3257                     break;
3258                   case H_GOT_REQ_HEADER:
3259                   case H_GOT_UNREQ_HEADER:
3260                   case H_GOT_UNWANTED_HEADER:
3261                   case H_GETTING_MOVES:
3262                     /* Should not happen */
3263                     DisplayError(_("Error gathering move list: two headers"), 0);
3264                     ics_getting_history = H_FALSE;
3265                     break;
3266                 }
3267
3268                 /* Save player ratings into gameInfo if needed */
3269                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3270                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3271                     (gameInfo.whiteRating == -1 ||
3272                      gameInfo.blackRating == -1)) {
3273
3274                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3275                     gameInfo.blackRating = string_to_rating(star_match[3]);
3276                     if (appData.debugMode)
3277                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3278                               gameInfo.whiteRating, gameInfo.blackRating);
3279                 }
3280                 continue;
3281             }
3282
3283             if (looking_at(buf, &i,
3284               "* * match, initial time: * minute*, increment: * second")) {
3285                 /* Header for a move list -- second line */
3286                 /* Initial board will follow if this is a wild game */
3287                 if (gameInfo.event != NULL) free(gameInfo.event);
3288                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3289                 gameInfo.event = StrSave(str);
3290                 /* [HGM] we switched variant. Translate boards if needed. */
3291                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3292                 continue;
3293             }
3294
3295             if (looking_at(buf, &i, "Move  ")) {
3296                 /* Beginning of a move list */
3297                 switch (ics_getting_history) {
3298                   case H_FALSE:
3299                     /* Normally should not happen */
3300                     /* Maybe user hit reset while we were parsing */
3301                     break;
3302                   case H_REQUESTED:
3303                     /* Happens if we are ignoring a move list that is not
3304                      * the one we just requested.  Common if the user
3305                      * tries to observe two games without turning off
3306                      * getMoveList */
3307                     break;
3308                   case H_GETTING_MOVES:
3309                     /* Should not happen */
3310                     DisplayError(_("Error gathering move list: nested"), 0);
3311                     ics_getting_history = H_FALSE;
3312                     break;
3313                   case H_GOT_REQ_HEADER:
3314                     ics_getting_history = H_GETTING_MOVES;
3315                     started = STARTED_MOVES;
3316                     parse_pos = 0;
3317                     if (oldi > next_out) {
3318                         SendToPlayer(&buf[next_out], oldi - next_out);
3319                     }
3320                     break;
3321                   case H_GOT_UNREQ_HEADER:
3322                     ics_getting_history = H_GETTING_MOVES;
3323                     started = STARTED_MOVES_NOHIDE;
3324                     parse_pos = 0;
3325                     break;
3326                   case H_GOT_UNWANTED_HEADER:
3327                     ics_getting_history = H_FALSE;
3328                     break;
3329                 }
3330                 continue;
3331             }
3332
3333             if (looking_at(buf, &i, "% ") ||
3334                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3335                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3336                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3337                     soughtPending = FALSE;
3338                     seekGraphUp = TRUE;
3339                     DrawSeekGraph();
3340                 }
3341                 if(suppressKibitz) next_out = i;
3342                 savingComment = FALSE;
3343                 suppressKibitz = 0;
3344                 switch (started) {
3345                   case STARTED_MOVES:
3346                   case STARTED_MOVES_NOHIDE:
3347                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3348                     parse[parse_pos + i - oldi] = NULLCHAR;
3349                     ParseGameHistory(parse);
3350 #if ZIPPY
3351                     if (appData.zippyPlay && first.initDone) {
3352                         FeedMovesToProgram(&first, forwardMostMove);
3353                         if (gameMode == IcsPlayingWhite) {
3354                             if (WhiteOnMove(forwardMostMove)) {
3355                                 if (first.sendTime) {
3356                                   if (first.useColors) {
3357                                     SendToProgram("black\n", &first);
3358                                   }
3359                                   SendTimeRemaining(&first, TRUE);
3360                                 }
3361                                 if (first.useColors) {
3362                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3363                                 }
3364                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3365                                 first.maybeThinking = TRUE;
3366                             } else {
3367                                 if (first.usePlayother) {
3368                                   if (first.sendTime) {
3369                                     SendTimeRemaining(&first, TRUE);
3370                                   }
3371                                   SendToProgram("playother\n", &first);
3372                                   firstMove = FALSE;
3373                                 } else {
3374                                   firstMove = TRUE;
3375                                 }
3376                             }
3377                         } else if (gameMode == IcsPlayingBlack) {
3378                             if (!WhiteOnMove(forwardMostMove)) {
3379                                 if (first.sendTime) {
3380                                   if (first.useColors) {
3381                                     SendToProgram("white\n", &first);
3382                                   }
3383                                   SendTimeRemaining(&first, FALSE);
3384                                 }
3385                                 if (first.useColors) {
3386                                   SendToProgram("black\n", &first);
3387                                 }
3388                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3389                                 first.maybeThinking = TRUE;
3390                             } else {
3391                                 if (first.usePlayother) {
3392                                   if (first.sendTime) {
3393                                     SendTimeRemaining(&first, FALSE);
3394                                   }
3395                                   SendToProgram("playother\n", &first);
3396                                   firstMove = FALSE;
3397                                 } else {
3398                                   firstMove = TRUE;
3399                                 }
3400                             }
3401                         }
3402                     }
3403 #endif
3404                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3405                         /* Moves came from oldmoves or moves command
3406                            while we weren't doing anything else.
3407                            */
3408                         currentMove = forwardMostMove;
3409                         ClearHighlights();/*!!could figure this out*/
3410                         flipView = appData.flipView;
3411                         DrawPosition(TRUE, boards[currentMove]);
3412                         DisplayBothClocks();
3413                         snprintf(str, MSG_SIZ, "%s vs. %s",
3414                                 gameInfo.white, gameInfo.black);
3415                         DisplayTitle(str);
3416                         gameMode = IcsIdle;
3417                     } else {
3418                         /* Moves were history of an active game */
3419                         if (gameInfo.resultDetails != NULL) {
3420                             free(gameInfo.resultDetails);
3421                             gameInfo.resultDetails = NULL;
3422                         }
3423                     }
3424                     HistorySet(parseList, backwardMostMove,
3425                                forwardMostMove, currentMove-1);
3426                     DisplayMove(currentMove - 1);
3427                     if (started == STARTED_MOVES) next_out = i;
3428                     started = STARTED_NONE;
3429                     ics_getting_history = H_FALSE;
3430                     break;
3431
3432                   case STARTED_OBSERVE:
3433                     started = STARTED_NONE;
3434                     SendToICS(ics_prefix);
3435                     SendToICS("refresh\n");
3436                     break;
3437
3438                   default:
3439                     break;
3440                 }
3441                 if(bookHit) { // [HGM] book: simulate book reply
3442                     static char bookMove[MSG_SIZ]; // a bit generous?
3443
3444                     programStats.nodes = programStats.depth = programStats.time =
3445                     programStats.score = programStats.got_only_move = 0;
3446                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3447
3448                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3449                     strcat(bookMove, bookHit);
3450                     HandleMachineMove(bookMove, &first);
3451                 }
3452                 continue;
3453             }
3454
3455             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3456                  started == STARTED_HOLDINGS ||
3457                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3458                 /* Accumulate characters in move list or board */
3459                 parse[parse_pos++] = buf[i];
3460             }
3461
3462             /* Start of game messages.  Mostly we detect start of game
3463                when the first board image arrives.  On some versions
3464                of the ICS, though, we need to do a "refresh" after starting
3465                to observe in order to get the current board right away. */
3466             if (looking_at(buf, &i, "Adding game * to observation list")) {
3467                 started = STARTED_OBSERVE;
3468                 continue;
3469             }
3470
3471             /* Handle auto-observe */
3472             if (appData.autoObserve &&
3473                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3474                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3475                 char *player;
3476                 /* Choose the player that was highlighted, if any. */
3477                 if (star_match[0][0] == '\033' ||
3478                     star_match[1][0] != '\033') {
3479                     player = star_match[0];
3480                 } else {
3481                     player = star_match[2];
3482                 }
3483                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3484                         ics_prefix, StripHighlightAndTitle(player));
3485                 SendToICS(str);
3486
3487                 /* Save ratings from notify string */
3488                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3489                 player1Rating = string_to_rating(star_match[1]);
3490                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3491                 player2Rating = string_to_rating(star_match[3]);
3492
3493                 if (appData.debugMode)
3494                   fprintf(debugFP,
3495                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3496                           player1Name, player1Rating,
3497                           player2Name, player2Rating);
3498
3499                 continue;
3500             }
3501
3502             /* Deal with automatic examine mode after a game,
3503                and with IcsObserving -> IcsExamining transition */
3504             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3505                 looking_at(buf, &i, "has made you an examiner of game *")) {
3506
3507                 int gamenum = atoi(star_match[0]);
3508                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3509                     gamenum == ics_gamenum) {
3510                     /* We were already playing or observing this game;
3511                        no need to refetch history */
3512                     gameMode = IcsExamining;
3513                     if (pausing) {
3514                         pauseExamForwardMostMove = forwardMostMove;
3515                     } else if (currentMove < forwardMostMove) {
3516                         ForwardInner(forwardMostMove);
3517                     }
3518                 } else {
3519                     /* I don't think this case really can happen */
3520                     SendToICS(ics_prefix);
3521                     SendToICS("refresh\n");
3522                 }
3523                 continue;
3524             }
3525
3526             /* Error messages */
3527 //          if (ics_user_moved) {
3528             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3529                 if (looking_at(buf, &i, "Illegal move") ||
3530                     looking_at(buf, &i, "Not a legal move") ||
3531                     looking_at(buf, &i, "Your king is in check") ||
3532                     looking_at(buf, &i, "It isn't your turn") ||
3533                     looking_at(buf, &i, "It is not your move")) {
3534                     /* Illegal move */
3535                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3536                         currentMove = forwardMostMove-1;
3537                         DisplayMove(currentMove - 1); /* before DMError */
3538                         DrawPosition(FALSE, boards[currentMove]);
3539                         SwitchClocks(forwardMostMove-1); // [HGM] race
3540                         DisplayBothClocks();
3541                     }
3542                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3543                     ics_user_moved = 0;
3544                     continue;
3545                 }
3546             }
3547
3548             if (looking_at(buf, &i, "still have time") ||
3549                 looking_at(buf, &i, "not out of time") ||
3550                 looking_at(buf, &i, "either player is out of time") ||
3551                 looking_at(buf, &i, "has timeseal; checking")) {
3552                 /* We must have called his flag a little too soon */
3553                 whiteFlag = blackFlag = FALSE;
3554                 continue;
3555             }
3556
3557             if (looking_at(buf, &i, "added * seconds to") ||
3558                 looking_at(buf, &i, "seconds were added to")) {
3559                 /* Update the clocks */
3560                 SendToICS(ics_prefix);
3561                 SendToICS("refresh\n");
3562                 continue;
3563             }
3564
3565             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3566                 ics_clock_paused = TRUE;
3567                 StopClocks();
3568                 continue;
3569             }
3570
3571             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3572                 ics_clock_paused = FALSE;
3573                 StartClocks();
3574                 continue;
3575             }
3576
3577             /* Grab player ratings from the Creating: message.
3578                Note we have to check for the special case when
3579                the ICS inserts things like [white] or [black]. */
3580             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3581                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3582                 /* star_matches:
3583                    0    player 1 name (not necessarily white)
3584                    1    player 1 rating
3585                    2    empty, white, or black (IGNORED)
3586                    3    player 2 name (not necessarily black)
3587                    4    player 2 rating
3588
3589                    The names/ratings are sorted out when the game
3590                    actually starts (below).
3591                 */
3592                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3593                 player1Rating = string_to_rating(star_match[1]);
3594                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3595                 player2Rating = string_to_rating(star_match[4]);
3596
3597                 if (appData.debugMode)
3598                   fprintf(debugFP,
3599                           "Ratings from 'Creating:' %s %d, %s %d\n",
3600                           player1Name, player1Rating,
3601                           player2Name, player2Rating);
3602
3603                 continue;
3604             }
3605
3606             /* Improved generic start/end-of-game messages */
3607             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3608                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3609                 /* If tkind == 0: */
3610                 /* star_match[0] is the game number */
3611                 /*           [1] is the white player's name */
3612                 /*           [2] is the black player's name */
3613                 /* For end-of-game: */
3614                 /*           [3] is the reason for the game end */
3615                 /*           [4] is a PGN end game-token, preceded by " " */
3616                 /* For start-of-game: */
3617                 /*           [3] begins with "Creating" or "Continuing" */
3618                 /*           [4] is " *" or empty (don't care). */
3619                 int gamenum = atoi(star_match[0]);
3620                 char *whitename, *blackname, *why, *endtoken;
3621                 ChessMove endtype = EndOfFile;
3622
3623                 if (tkind == 0) {
3624                   whitename = star_match[1];
3625                   blackname = star_match[2];
3626                   why = star_match[3];
3627                   endtoken = star_match[4];
3628                 } else {
3629                   whitename = star_match[1];
3630                   blackname = star_match[3];
3631                   why = star_match[5];
3632                   endtoken = star_match[6];
3633                 }
3634
3635                 /* Game start messages */
3636                 if (strncmp(why, "Creating ", 9) == 0 ||
3637                     strncmp(why, "Continuing ", 11) == 0) {
3638                     gs_gamenum = gamenum;
3639                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3640                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3641 #if ZIPPY
3642                     if (appData.zippyPlay) {
3643                         ZippyGameStart(whitename, blackname);
3644                     }
3645 #endif /*ZIPPY*/
3646                     partnerBoardValid = FALSE; // [HGM] bughouse
3647                     continue;
3648                 }
3649
3650                 /* Game end messages */
3651                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3652                     ics_gamenum != gamenum) {
3653                     continue;
3654                 }
3655                 while (endtoken[0] == ' ') endtoken++;
3656                 switch (endtoken[0]) {
3657                   case '*':
3658                   default:
3659                     endtype = GameUnfinished;
3660                     break;
3661                   case '0':
3662                     endtype = BlackWins;
3663                     break;
3664                   case '1':
3665                     if (endtoken[1] == '/')
3666                       endtype = GameIsDrawn;
3667                     else
3668                       endtype = WhiteWins;
3669                     break;
3670                 }
3671                 GameEnds(endtype, why, GE_ICS);
3672 #if ZIPPY
3673                 if (appData.zippyPlay && first.initDone) {
3674                     ZippyGameEnd(endtype, why);
3675                     if (first.pr == NULL) {
3676                       /* Start the next process early so that we'll
3677                          be ready for the next challenge */
3678                       StartChessProgram(&first);
3679                     }
3680                     /* Send "new" early, in case this command takes
3681                        a long time to finish, so that we'll be ready
3682                        for the next challenge. */
3683                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3684                     Reset(TRUE, TRUE);
3685                 }
3686 #endif /*ZIPPY*/
3687                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i, "Removing game * from observation") ||
3692                 looking_at(buf, &i, "no longer observing game *") ||
3693                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3694                 if (gameMode == IcsObserving &&
3695                     atoi(star_match[0]) == ics_gamenum)
3696                   {
3697                       /* icsEngineAnalyze */
3698                       if (appData.icsEngineAnalyze) {
3699                             ExitAnalyzeMode();
3700                             ModeHighlight();
3701                       }
3702                       StopClocks();
3703                       gameMode = IcsIdle;
3704                       ics_gamenum = -1;
3705                       ics_user_moved = FALSE;
3706                   }
3707                 continue;
3708             }
3709
3710             if (looking_at(buf, &i, "no longer examining game *")) {
3711                 if (gameMode == IcsExamining &&
3712                     atoi(star_match[0]) == ics_gamenum)
3713                   {
3714                       gameMode = IcsIdle;
3715                       ics_gamenum = -1;
3716                       ics_user_moved = FALSE;
3717                   }
3718                 continue;
3719             }
3720
3721             /* Advance leftover_start past any newlines we find,
3722                so only partial lines can get reparsed */
3723             if (looking_at(buf, &i, "\n")) {
3724                 prevColor = curColor;
3725                 if (curColor != ColorNormal) {
3726                     if (oldi > next_out) {
3727                         SendToPlayer(&buf[next_out], oldi - next_out);
3728                         next_out = oldi;
3729                     }
3730                     Colorize(ColorNormal, FALSE);
3731                     curColor = ColorNormal;
3732                 }
3733                 if (started == STARTED_BOARD) {
3734                     started = STARTED_NONE;
3735                     parse[parse_pos] = NULLCHAR;
3736                     ParseBoard12(parse);
3737                     ics_user_moved = 0;
3738
3739                     /* Send premove here */
3740                     if (appData.premove) {
3741                       char str[MSG_SIZ];
3742                       if (currentMove == 0 &&
3743                           gameMode == IcsPlayingWhite &&
3744                           appData.premoveWhite) {
3745                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3746                         if (appData.debugMode)
3747                           fprintf(debugFP, "Sending premove:\n");
3748                         SendToICS(str);
3749                       } else if (currentMove == 1 &&
3750                                  gameMode == IcsPlayingBlack &&
3751                                  appData.premoveBlack) {
3752                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3753                         if (appData.debugMode)
3754                           fprintf(debugFP, "Sending premove:\n");
3755                         SendToICS(str);
3756                       } else if (gotPremove) {
3757                         gotPremove = 0;
3758                         ClearPremoveHighlights();
3759                         if (appData.debugMode)
3760                           fprintf(debugFP, "Sending premove:\n");
3761                           UserMoveEvent(premoveFromX, premoveFromY,
3762                                         premoveToX, premoveToY,
3763                                         premovePromoChar);
3764                       }
3765                     }
3766
3767                     /* Usually suppress following prompt */
3768                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3769                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3770                         if (looking_at(buf, &i, "*% ")) {
3771                             savingComment = FALSE;
3772                             suppressKibitz = 0;
3773                         }
3774                     }
3775                     next_out = i;
3776                 } else if (started == STARTED_HOLDINGS) {
3777                     int gamenum;
3778                     char new_piece[MSG_SIZ];
3779                     started = STARTED_NONE;
3780                     parse[parse_pos] = NULLCHAR;
3781                     if (appData.debugMode)
3782                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3783                                                         parse, currentMove);
3784                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3785                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3786                         if (gameInfo.variant == VariantNormal) {
3787                           /* [HGM] We seem to switch variant during a game!
3788                            * Presumably no holdings were displayed, so we have
3789                            * to move the position two files to the right to
3790                            * create room for them!
3791                            */
3792                           VariantClass newVariant;
3793                           switch(gameInfo.boardWidth) { // base guess on board width
3794                                 case 9:  newVariant = VariantShogi; break;
3795                                 case 10: newVariant = VariantGreat; break;
3796                                 default: newVariant = VariantCrazyhouse; break;
3797                           }
3798                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3799                           /* Get a move list just to see the header, which
3800                              will tell us whether this is really bug or zh */
3801                           if (ics_getting_history == H_FALSE) {
3802                             ics_getting_history = H_REQUESTED;
3803                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3804                             SendToICS(str);
3805                           }
3806                         }
3807                         new_piece[0] = NULLCHAR;
3808                         sscanf(parse, "game %d white [%s black [%s <- %s",
3809                                &gamenum, white_holding, black_holding,
3810                                new_piece);
3811                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3812                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3813                         /* [HGM] copy holdings to board holdings area */
3814                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3815                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3816                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3817 #if ZIPPY
3818                         if (appData.zippyPlay && first.initDone) {
3819                             ZippyHoldings(white_holding, black_holding,
3820                                           new_piece);
3821                         }
3822 #endif /*ZIPPY*/
3823                         if (tinyLayout || smallLayout) {
3824                             char wh[16], bh[16];
3825                             PackHolding(wh, white_holding);
3826                             PackHolding(bh, black_holding);
3827                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3828                                     gameInfo.white, gameInfo.black);
3829                         } else {
3830                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3831                                     gameInfo.white, white_holding,
3832                                     gameInfo.black, black_holding);
3833                         }
3834                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3835                         DrawPosition(FALSE, boards[currentMove]);
3836                         DisplayTitle(str);
3837                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3838                         sscanf(parse, "game %d white [%s black [%s <- %s",
3839                                &gamenum, white_holding, black_holding,
3840                                new_piece);
3841                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3842                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3843                         /* [HGM] copy holdings to partner-board holdings area */
3844                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3845                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3846                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3847                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3848                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3849                       }
3850                     }
3851                     /* Suppress following prompt */
3852                     if (looking_at(buf, &i, "*% ")) {
3853                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3854                         savingComment = FALSE;
3855                         suppressKibitz = 0;
3856                     }
3857                     next_out = i;
3858                 }
3859                 continue;
3860             }
3861
3862             i++;                /* skip unparsed character and loop back */
3863         }
3864
3865         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3866 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3867 //          SendToPlayer(&buf[next_out], i - next_out);
3868             started != STARTED_HOLDINGS && leftover_start > next_out) {
3869             SendToPlayer(&buf[next_out], leftover_start - next_out);
3870             next_out = i;
3871         }
3872
3873         leftover_len = buf_len - leftover_start;
3874         /* if buffer ends with something we couldn't parse,
3875            reparse it after appending the next read */
3876
3877     } else if (count == 0) {
3878         RemoveInputSource(isr);
3879         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3880     } else {
3881         DisplayFatalError(_("Error reading from ICS"), error, 1);
3882     }
3883 }
3884
3885
3886 /* Board style 12 looks like this:
3887
3888    <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
3889
3890  * The "<12> " is stripped before it gets to this routine.  The two
3891  * trailing 0's (flip state and clock ticking) are later addition, and
3892  * some chess servers may not have them, or may have only the first.
3893  * Additional trailing fields may be added in the future.
3894  */
3895
3896 #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"
3897
3898 #define RELATION_OBSERVING_PLAYED    0
3899 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3900 #define RELATION_PLAYING_MYMOVE      1
3901 #define RELATION_PLAYING_NOTMYMOVE  -1
3902 #define RELATION_EXAMINING           2
3903 #define RELATION_ISOLATED_BOARD     -3
3904 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3905
3906 void
3907 ParseBoard12(string)
3908      char *string;
3909 {
3910     GameMode newGameMode;
3911     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3912     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3913     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3914     char to_play, board_chars[200];
3915     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3916     char black[32], white[32];
3917     Board board;
3918     int prevMove = currentMove;
3919     int ticking = 2;
3920     ChessMove moveType;
3921     int fromX, fromY, toX, toY;
3922     char promoChar;
3923     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3924     char *bookHit = NULL; // [HGM] book
3925     Boolean weird = FALSE, reqFlag = FALSE;
3926
3927     fromX = fromY = toX = toY = -1;
3928
3929     newGame = FALSE;
3930
3931     if (appData.debugMode)
3932       fprintf(debugFP, _("Parsing board: %s\n"), string);
3933
3934     move_str[0] = NULLCHAR;
3935     elapsed_time[0] = NULLCHAR;
3936     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3937         int  i = 0, j;
3938         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3939             if(string[i] == ' ') { ranks++; files = 0; }
3940             else files++;
3941             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3942             i++;
3943         }
3944         for(j = 0; j <i; j++) board_chars[j] = string[j];
3945         board_chars[i] = '\0';
3946         string += i + 1;
3947     }
3948     n = sscanf(string, PATTERN, &to_play, &double_push,
3949                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3950                &gamenum, white, black, &relation, &basetime, &increment,
3951                &white_stren, &black_stren, &white_time, &black_time,
3952                &moveNum, str, elapsed_time, move_str, &ics_flip,
3953                &ticking);
3954
3955     if (n < 21) {
3956         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3957         DisplayError(str, 0);
3958         return;
3959     }
3960
3961     /* Convert the move number to internal form */
3962     moveNum = (moveNum - 1) * 2;
3963     if (to_play == 'B') moveNum++;
3964     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3965       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3966                         0, 1);
3967       return;
3968     }
3969
3970     switch (relation) {
3971       case RELATION_OBSERVING_PLAYED:
3972       case RELATION_OBSERVING_STATIC:
3973         if (gamenum == -1) {
3974             /* Old ICC buglet */
3975             relation = RELATION_OBSERVING_STATIC;
3976         }
3977         newGameMode = IcsObserving;
3978         break;
3979       case RELATION_PLAYING_MYMOVE:
3980       case RELATION_PLAYING_NOTMYMOVE:
3981         newGameMode =
3982           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3983             IcsPlayingWhite : IcsPlayingBlack;
3984         break;
3985       case RELATION_EXAMINING:
3986         newGameMode = IcsExamining;
3987         break;
3988       case RELATION_ISOLATED_BOARD:
3989       default:
3990         /* Just display this board.  If user was doing something else,
3991            we will forget about it until the next board comes. */
3992         newGameMode = IcsIdle;
3993         break;
3994       case RELATION_STARTING_POSITION:
3995         newGameMode = gameMode;
3996         break;
3997     }
3998
3999     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4000          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4001       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4002       char *toSqr;
4003       for (k = 0; k < ranks; k++) {
4004         for (j = 0; j < files; j++)
4005           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4006         if(gameInfo.holdingsWidth > 1) {
4007              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4008              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4009         }
4010       }
4011       CopyBoard(partnerBoard, board);
4012       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4013         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4014         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4015       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4016       if(toSqr = strchr(str, '-')) {
4017         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4018         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4019       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4020       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4021       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4022       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4023       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4024       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4025                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4026       DisplayMessage(partnerStatus, "");
4027         partnerBoardValid = TRUE;
4028       return;
4029     }
4030
4031     /* Modify behavior for initial board display on move listing
4032        of wild games.
4033        */
4034     switch (ics_getting_history) {
4035       case H_FALSE:
4036       case H_REQUESTED:
4037         break;
4038       case H_GOT_REQ_HEADER:
4039       case H_GOT_UNREQ_HEADER:
4040         /* This is the initial position of the current game */
4041         gamenum = ics_gamenum;
4042         moveNum = 0;            /* old ICS bug workaround */
4043         if (to_play == 'B') {
4044           startedFromSetupPosition = TRUE;
4045           blackPlaysFirst = TRUE;
4046           moveNum = 1;
4047           if (forwardMostMove == 0) forwardMostMove = 1;
4048           if (backwardMostMove == 0) backwardMostMove = 1;
4049           if (currentMove == 0) currentMove = 1;
4050         }
4051         newGameMode = gameMode;
4052         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4053         break;
4054       case H_GOT_UNWANTED_HEADER:
4055         /* This is an initial board that we don't want */
4056         return;
4057       case H_GETTING_MOVES:
4058         /* Should not happen */
4059         DisplayError(_("Error gathering move list: extra board"), 0);
4060         ics_getting_history = H_FALSE;
4061         return;
4062     }
4063
4064    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4065                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4066      /* [HGM] We seem to have switched variant unexpectedly
4067       * Try to guess new variant from board size
4068       */
4069           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4070           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4071           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4072           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4073           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4074           if(!weird) newVariant = VariantNormal;
4075           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4076           /* Get a move list just to see the header, which
4077              will tell us whether this is really bug or zh */
4078           if (ics_getting_history == H_FALSE) {
4079             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4080             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4081             SendToICS(str);
4082           }
4083     }
4084
4085     /* Take action if this is the first board of a new game, or of a
4086        different game than is currently being displayed.  */
4087     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4088         relation == RELATION_ISOLATED_BOARD) {
4089
4090         /* Forget the old game and get the history (if any) of the new one */
4091         if (gameMode != BeginningOfGame) {
4092           Reset(TRUE, TRUE);
4093         }
4094         newGame = TRUE;
4095         if (appData.autoRaiseBoard) BoardToTop();
4096         prevMove = -3;
4097         if (gamenum == -1) {
4098             newGameMode = IcsIdle;
4099         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4100                    appData.getMoveList && !reqFlag) {
4101             /* Need to get game history */
4102             ics_getting_history = H_REQUESTED;
4103             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4104             SendToICS(str);
4105         }
4106
4107         /* Initially flip the board to have black on the bottom if playing
4108            black or if the ICS flip flag is set, but let the user change
4109            it with the Flip View button. */
4110         flipView = appData.autoFlipView ?
4111           (newGameMode == IcsPlayingBlack) || ics_flip :
4112           appData.flipView;
4113
4114         /* Done with values from previous mode; copy in new ones */
4115         gameMode = newGameMode;
4116         ModeHighlight();
4117         ics_gamenum = gamenum;
4118         if (gamenum == gs_gamenum) {
4119             int klen = strlen(gs_kind);
4120             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4121             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4122             gameInfo.event = StrSave(str);
4123         } else {
4124             gameInfo.event = StrSave("ICS game");
4125         }
4126         gameInfo.site = StrSave(appData.icsHost);
4127         gameInfo.date = PGNDate();
4128         gameInfo.round = StrSave("-");
4129         gameInfo.white = StrSave(white);
4130         gameInfo.black = StrSave(black);
4131         timeControl = basetime * 60 * 1000;
4132         timeControl_2 = 0;
4133         timeIncrement = increment * 1000;
4134         movesPerSession = 0;
4135         gameInfo.timeControl = TimeControlTagValue();
4136         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4137   if (appData.debugMode) {
4138     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4139     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4140     setbuf(debugFP, NULL);
4141   }
4142
4143         gameInfo.outOfBook = NULL;
4144
4145         /* Do we have the ratings? */
4146         if (strcmp(player1Name, white) == 0 &&
4147             strcmp(player2Name, black) == 0) {
4148             if (appData.debugMode)
4149               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4150                       player1Rating, player2Rating);
4151             gameInfo.whiteRating = player1Rating;
4152             gameInfo.blackRating = player2Rating;
4153         } else if (strcmp(player2Name, white) == 0 &&
4154                    strcmp(player1Name, black) == 0) {
4155             if (appData.debugMode)
4156               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4157                       player2Rating, player1Rating);
4158             gameInfo.whiteRating = player2Rating;
4159             gameInfo.blackRating = player1Rating;
4160         }
4161         player1Name[0] = player2Name[0] = NULLCHAR;
4162
4163         /* Silence shouts if requested */
4164         if (appData.quietPlay &&
4165             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4166             SendToICS(ics_prefix);
4167             SendToICS("set shout 0\n");
4168         }
4169     }
4170
4171     /* Deal with midgame name changes */
4172     if (!newGame) {
4173         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4174             if (gameInfo.white) free(gameInfo.white);
4175             gameInfo.white = StrSave(white);
4176         }
4177         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4178             if (gameInfo.black) free(gameInfo.black);
4179             gameInfo.black = StrSave(black);
4180         }
4181     }
4182
4183     /* Throw away game result if anything actually changes in examine mode */
4184     if (gameMode == IcsExamining && !newGame) {
4185         gameInfo.result = GameUnfinished;
4186         if (gameInfo.resultDetails != NULL) {
4187             free(gameInfo.resultDetails);
4188             gameInfo.resultDetails = NULL;
4189         }
4190     }
4191
4192     /* In pausing && IcsExamining mode, we ignore boards coming
4193        in if they are in a different variation than we are. */
4194     if (pauseExamInvalid) return;
4195     if (pausing && gameMode == IcsExamining) {
4196         if (moveNum <= pauseExamForwardMostMove) {
4197             pauseExamInvalid = TRUE;
4198             forwardMostMove = pauseExamForwardMostMove;
4199             return;
4200         }
4201     }
4202
4203   if (appData.debugMode) {
4204     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4205   }
4206     /* Parse the board */
4207     for (k = 0; k < ranks; k++) {
4208       for (j = 0; j < files; j++)
4209         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4210       if(gameInfo.holdingsWidth > 1) {
4211            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4212            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4213       }
4214     }
4215     CopyBoard(boards[moveNum], board);
4216     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4217     if (moveNum == 0) {
4218         startedFromSetupPosition =
4219           !CompareBoards(board, initialPosition);
4220         if(startedFromSetupPosition)
4221             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4222     }
4223
4224     /* [HGM] Set castling rights. Take the outermost Rooks,
4225        to make it also work for FRC opening positions. Note that board12
4226        is really defective for later FRC positions, as it has no way to
4227        indicate which Rook can castle if they are on the same side of King.
4228        For the initial position we grant rights to the outermost Rooks,
4229        and remember thos rights, and we then copy them on positions
4230        later in an FRC game. This means WB might not recognize castlings with
4231        Rooks that have moved back to their original position as illegal,
4232        but in ICS mode that is not its job anyway.
4233     */
4234     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4235     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4236
4237         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4238             if(board[0][i] == WhiteRook) j = i;
4239         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4240         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4241             if(board[0][i] == WhiteRook) j = i;
4242         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4243         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4244             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4245         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4246         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4247             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4248         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4249
4250         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4251         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4252             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4253         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4254             if(board[BOARD_HEIGHT-1][k] == bKing)
4255                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4256         if(gameInfo.variant == VariantTwoKings) {
4257             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4258             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4259             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4260         }
4261     } else { int r;
4262         r = boards[moveNum][CASTLING][0] = initialRights[0];
4263         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4264         r = boards[moveNum][CASTLING][1] = initialRights[1];
4265         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4266         r = boards[moveNum][CASTLING][3] = initialRights[3];
4267         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4268         r = boards[moveNum][CASTLING][4] = initialRights[4];
4269         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4270         /* wildcastle kludge: always assume King has rights */
4271         r = boards[moveNum][CASTLING][2] = initialRights[2];
4272         r = boards[moveNum][CASTLING][5] = initialRights[5];
4273     }
4274     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4275     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4276
4277
4278     if (ics_getting_history == H_GOT_REQ_HEADER ||
4279         ics_getting_history == H_GOT_UNREQ_HEADER) {
4280         /* This was an initial position from a move list, not
4281            the current position */
4282         return;
4283     }
4284
4285     /* Update currentMove and known move number limits */
4286     newMove = newGame || moveNum > forwardMostMove;
4287
4288     if (newGame) {
4289         forwardMostMove = backwardMostMove = currentMove = moveNum;
4290         if (gameMode == IcsExamining && moveNum == 0) {
4291           /* Workaround for ICS limitation: we are not told the wild
4292              type when starting to examine a game.  But if we ask for
4293              the move list, the move list header will tell us */
4294             ics_getting_history = H_REQUESTED;
4295             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4296             SendToICS(str);
4297         }
4298     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4299                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4300 #if ZIPPY
4301         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4302         /* [HGM] applied this also to an engine that is silently watching        */
4303         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4304             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4305             gameInfo.variant == currentlyInitializedVariant) {
4306           takeback = forwardMostMove - moveNum;
4307           for (i = 0; i < takeback; i++) {
4308             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4309             SendToProgram("undo\n", &first);
4310           }
4311         }
4312 #endif
4313
4314         forwardMostMove = moveNum;
4315         if (!pausing || currentMove > forwardMostMove)
4316           currentMove = forwardMostMove;
4317     } else {
4318         /* New part of history that is not contiguous with old part */
4319         if (pausing && gameMode == IcsExamining) {
4320             pauseExamInvalid = TRUE;
4321             forwardMostMove = pauseExamForwardMostMove;
4322             return;
4323         }
4324         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4325 #if ZIPPY
4326             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4327                 // [HGM] when we will receive the move list we now request, it will be
4328                 // fed to the engine from the first move on. So if the engine is not
4329                 // in the initial position now, bring it there.
4330                 InitChessProgram(&first, 0);
4331             }
4332 #endif
4333             ics_getting_history = H_REQUESTED;
4334             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4335             SendToICS(str);
4336         }
4337         forwardMostMove = backwardMostMove = currentMove = moveNum;
4338     }
4339
4340     /* Update the clocks */
4341     if (strchr(elapsed_time, '.')) {
4342       /* Time is in ms */
4343       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4344       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4345     } else {
4346       /* Time is in seconds */
4347       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4348       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4349     }
4350
4351
4352 #if ZIPPY
4353     if (appData.zippyPlay && newGame &&
4354         gameMode != IcsObserving && gameMode != IcsIdle &&
4355         gameMode != IcsExamining)
4356       ZippyFirstBoard(moveNum, basetime, increment);
4357 #endif
4358
4359     /* Put the move on the move list, first converting
4360        to canonical algebraic form. */
4361     if (moveNum > 0) {
4362   if (appData.debugMode) {
4363     if (appData.debugMode) { int f = forwardMostMove;
4364         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4365                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4366                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4367     }
4368     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4369     fprintf(debugFP, "moveNum = %d\n", moveNum);
4370     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4371     setbuf(debugFP, NULL);
4372   }
4373         if (moveNum <= backwardMostMove) {
4374             /* We don't know what the board looked like before
4375                this move.  Punt. */
4376           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4377             strcat(parseList[moveNum - 1], " ");
4378             strcat(parseList[moveNum - 1], elapsed_time);
4379             moveList[moveNum - 1][0] = NULLCHAR;
4380         } else if (strcmp(move_str, "none") == 0) {
4381             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4382             /* Again, we don't know what the board looked like;
4383                this is really the start of the game. */
4384             parseList[moveNum - 1][0] = NULLCHAR;
4385             moveList[moveNum - 1][0] = NULLCHAR;
4386             backwardMostMove = moveNum;
4387             startedFromSetupPosition = TRUE;
4388             fromX = fromY = toX = toY = -1;
4389         } else {
4390           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4391           //                 So we parse the long-algebraic move string in stead of the SAN move
4392           int valid; char buf[MSG_SIZ], *prom;
4393
4394           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4395                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4396           // str looks something like "Q/a1-a2"; kill the slash
4397           if(str[1] == '/')
4398             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4399           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4400           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4401                 strcat(buf, prom); // long move lacks promo specification!
4402           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4403                 if(appData.debugMode)
4404                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4405                 safeStrCpy(move_str, buf, MSG_SIZ);
4406           }
4407           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4408                                 &fromX, &fromY, &toX, &toY, &promoChar)
4409                || ParseOneMove(buf, moveNum - 1, &moveType,
4410                                 &fromX, &fromY, &toX, &toY, &promoChar);
4411           // end of long SAN patch
4412           if (valid) {
4413             (void) CoordsToAlgebraic(boards[moveNum - 1],
4414                                      PosFlags(moveNum - 1),
4415                                      fromY, fromX, toY, toX, promoChar,
4416                                      parseList[moveNum-1]);
4417             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4418               case MT_NONE:
4419               case MT_STALEMATE:
4420               default:
4421                 break;
4422               case MT_CHECK:
4423                 if(gameInfo.variant != VariantShogi)
4424                     strcat(parseList[moveNum - 1], "+");
4425                 break;
4426               case MT_CHECKMATE:
4427               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4428                 strcat(parseList[moveNum - 1], "#");
4429                 break;
4430             }
4431             strcat(parseList[moveNum - 1], " ");
4432             strcat(parseList[moveNum - 1], elapsed_time);
4433             /* currentMoveString is set as a side-effect of ParseOneMove */
4434             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4435             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4436             strcat(moveList[moveNum - 1], "\n");
4437
4438             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4439                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4440               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4441                 ChessSquare old, new = boards[moveNum][k][j];
4442                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4443                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4444                   if(old == new) continue;
4445                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4446                   else if(new == WhiteWazir || new == BlackWazir) {
4447                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4448                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4449                       else boards[moveNum][k][j] = old; // preserve type of Gold
4450                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4451                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4452               }
4453           } else {
4454             /* Move from ICS was illegal!?  Punt. */
4455             if (appData.debugMode) {
4456               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4457               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4458             }
4459             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4460             strcat(parseList[moveNum - 1], " ");
4461             strcat(parseList[moveNum - 1], elapsed_time);
4462             moveList[moveNum - 1][0] = NULLCHAR;
4463             fromX = fromY = toX = toY = -1;
4464           }
4465         }
4466   if (appData.debugMode) {
4467     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4468     setbuf(debugFP, NULL);
4469   }
4470
4471 #if ZIPPY
4472         /* Send move to chess program (BEFORE animating it). */
4473         if (appData.zippyPlay && !newGame && newMove &&
4474            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4475
4476             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4477                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4478                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4479                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4480                             move_str);
4481                     DisplayError(str, 0);
4482                 } else {
4483                     if (first.sendTime) {
4484                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4485                     }
4486                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4487                     if (firstMove && !bookHit) {
4488                         firstMove = FALSE;
4489                         if (first.useColors) {
4490                           SendToProgram(gameMode == IcsPlayingWhite ?
4491                                         "white\ngo\n" :
4492                                         "black\ngo\n", &first);
4493                         } else {
4494                           SendToProgram("go\n", &first);
4495                         }
4496                         first.maybeThinking = TRUE;
4497                     }
4498                 }
4499             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4500               if (moveList[moveNum - 1][0] == NULLCHAR) {
4501                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4502                 DisplayError(str, 0);
4503               } else {
4504                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4505                 SendMoveToProgram(moveNum - 1, &first);
4506               }
4507             }
4508         }
4509 #endif
4510     }
4511
4512     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4513         /* If move comes from a remote source, animate it.  If it
4514            isn't remote, it will have already been animated. */
4515         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4516             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4517         }
4518         if (!pausing && appData.highlightLastMove) {
4519             SetHighlights(fromX, fromY, toX, toY);
4520         }
4521     }
4522
4523     /* Start the clocks */
4524     whiteFlag = blackFlag = FALSE;
4525     appData.clockMode = !(basetime == 0 && increment == 0);
4526     if (ticking == 0) {
4527       ics_clock_paused = TRUE;
4528       StopClocks();
4529     } else if (ticking == 1) {
4530       ics_clock_paused = FALSE;
4531     }
4532     if (gameMode == IcsIdle ||
4533         relation == RELATION_OBSERVING_STATIC ||
4534         relation == RELATION_EXAMINING ||
4535         ics_clock_paused)
4536       DisplayBothClocks();
4537     else
4538       StartClocks();
4539
4540     /* Display opponents and material strengths */
4541     if (gameInfo.variant != VariantBughouse &&
4542         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4543         if (tinyLayout || smallLayout) {
4544             if(gameInfo.variant == VariantNormal)
4545               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4546                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4547                     basetime, increment);
4548             else
4549               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4550                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4551                     basetime, increment, (int) gameInfo.variant);
4552         } else {
4553             if(gameInfo.variant == VariantNormal)
4554               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4555                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4556                     basetime, increment);
4557             else
4558               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4559                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4560                     basetime, increment, VariantName(gameInfo.variant));
4561         }
4562         DisplayTitle(str);
4563   if (appData.debugMode) {
4564     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4565   }
4566     }
4567
4568
4569     /* Display the board */
4570     if (!pausing && !appData.noGUI) {
4571
4572       if (appData.premove)
4573           if (!gotPremove ||
4574              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4575              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4576               ClearPremoveHighlights();
4577
4578       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4579         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4580       DrawPosition(j, boards[currentMove]);
4581
4582       DisplayMove(moveNum - 1);
4583       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4584             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4585               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4586         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4587       }
4588     }
4589
4590     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4591 #if ZIPPY
4592     if(bookHit) { // [HGM] book: simulate book reply
4593         static char bookMove[MSG_SIZ]; // a bit generous?
4594
4595         programStats.nodes = programStats.depth = programStats.time =
4596         programStats.score = programStats.got_only_move = 0;
4597         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4598
4599         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4600         strcat(bookMove, bookHit);
4601         HandleMachineMove(bookMove, &first);
4602     }
4603 #endif
4604 }
4605
4606 void
4607 GetMoveListEvent()
4608 {
4609     char buf[MSG_SIZ];
4610     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4611         ics_getting_history = H_REQUESTED;
4612         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4613         SendToICS(buf);
4614     }
4615 }
4616
4617 void
4618 AnalysisPeriodicEvent(force)
4619      int force;
4620 {
4621     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4622          && !force) || !appData.periodicUpdates)
4623       return;
4624
4625     /* Send . command to Crafty to collect stats */
4626     SendToProgram(".\n", &first);
4627
4628     /* Don't send another until we get a response (this makes
4629        us stop sending to old Crafty's which don't understand
4630        the "." command (sending illegal cmds resets node count & time,
4631        which looks bad)) */
4632     programStats.ok_to_send = 0;
4633 }
4634
4635 void ics_update_width(new_width)
4636         int new_width;
4637 {
4638         ics_printf("set width %d\n", new_width);
4639 }
4640
4641 void
4642 SendMoveToProgram(moveNum, cps)
4643      int moveNum;
4644      ChessProgramState *cps;
4645 {
4646     char buf[MSG_SIZ];
4647
4648     if (cps->useUsermove) {
4649       SendToProgram("usermove ", cps);
4650     }
4651     if (cps->useSAN) {
4652       char *space;
4653       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4654         int len = space - parseList[moveNum];
4655         memcpy(buf, parseList[moveNum], len);
4656         buf[len++] = '\n';
4657         buf[len] = NULLCHAR;
4658       } else {
4659         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4660       }
4661       SendToProgram(buf, cps);
4662     } else {
4663       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4664         AlphaRank(moveList[moveNum], 4);
4665         SendToProgram(moveList[moveNum], cps);
4666         AlphaRank(moveList[moveNum], 4); // and back
4667       } else
4668       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4669        * the engine. It would be nice to have a better way to identify castle
4670        * moves here. */
4671       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4672                                                                          && cps->useOOCastle) {
4673         int fromX = moveList[moveNum][0] - AAA;
4674         int fromY = moveList[moveNum][1] - ONE;
4675         int toX = moveList[moveNum][2] - AAA;
4676         int toY = moveList[moveNum][3] - ONE;
4677         if((boards[moveNum][fromY][fromX] == WhiteKing
4678             && boards[moveNum][toY][toX] == WhiteRook)
4679            || (boards[moveNum][fromY][fromX] == BlackKing
4680                && boards[moveNum][toY][toX] == BlackRook)) {
4681           if(toX > fromX) SendToProgram("O-O\n", cps);
4682           else SendToProgram("O-O-O\n", cps);
4683         }
4684         else SendToProgram(moveList[moveNum], cps);
4685       }
4686       else SendToProgram(moveList[moveNum], cps);
4687       /* End of additions by Tord */
4688     }
4689
4690     /* [HGM] setting up the opening has brought engine in force mode! */
4691     /*       Send 'go' if we are in a mode where machine should play. */
4692     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4693         (gameMode == TwoMachinesPlay   ||
4694 #if ZIPPY
4695          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4696 #endif
4697          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4698         SendToProgram("go\n", cps);
4699   if (appData.debugMode) {
4700     fprintf(debugFP, "(extra)\n");
4701   }
4702     }
4703     setboardSpoiledMachineBlack = 0;
4704 }
4705
4706 void
4707 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4708      ChessMove moveType;
4709      int fromX, fromY, toX, toY;
4710      char promoChar;
4711 {
4712     char user_move[MSG_SIZ];
4713
4714     switch (moveType) {
4715       default:
4716         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4717                 (int)moveType, fromX, fromY, toX, toY);
4718         DisplayError(user_move + strlen("say "), 0);
4719         break;
4720       case WhiteKingSideCastle:
4721       case BlackKingSideCastle:
4722       case WhiteQueenSideCastleWild:
4723       case BlackQueenSideCastleWild:
4724       /* PUSH Fabien */
4725       case WhiteHSideCastleFR:
4726       case BlackHSideCastleFR:
4727       /* POP Fabien */
4728         snprintf(user_move, MSG_SIZ, "o-o\n");
4729         break;
4730       case WhiteQueenSideCastle:
4731       case BlackQueenSideCastle:
4732       case WhiteKingSideCastleWild:
4733       case BlackKingSideCastleWild:
4734       /* PUSH Fabien */
4735       case WhiteASideCastleFR:
4736       case BlackASideCastleFR:
4737       /* POP Fabien */
4738         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4739         break;
4740       case WhiteNonPromotion:
4741       case BlackNonPromotion:
4742         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4743         break;
4744       case WhitePromotion:
4745       case BlackPromotion:
4746         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4747           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4748                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4749                 PieceToChar(WhiteFerz));
4750         else if(gameInfo.variant == VariantGreat)
4751           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4752                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4753                 PieceToChar(WhiteMan));
4754         else
4755           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4756                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4757                 promoChar);
4758         break;
4759       case WhiteDrop:
4760       case BlackDrop:
4761       drop:
4762         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4763                  ToUpper(PieceToChar((ChessSquare) fromX)),
4764                  AAA + toX, ONE + toY);
4765         break;
4766       case IllegalMove:  /* could be a variant we don't quite understand */
4767         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4768       case NormalMove:
4769       case WhiteCapturesEnPassant:
4770       case BlackCapturesEnPassant:
4771         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4772                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4773         break;
4774     }
4775     SendToICS(user_move);
4776     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4777         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4778 }
4779
4780 void
4781 UploadGameEvent()
4782 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4783     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4784     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4785     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4786         DisplayError("You cannot do this while you are playing or observing", 0);
4787         return;
4788     }
4789     if(gameMode != IcsExamining) { // is this ever not the case?
4790         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4791
4792         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4793           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4794         } else { // on FICS we must first go to general examine mode
4795           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4796         }
4797         if(gameInfo.variant != VariantNormal) {
4798             // try figure out wild number, as xboard names are not always valid on ICS
4799             for(i=1; i<=36; i++) {
4800               snprintf(buf, MSG_SIZ, "wild/%d", i);
4801                 if(StringToVariant(buf) == gameInfo.variant) break;
4802             }
4803             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4804             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4805             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4806         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4807         SendToICS(ics_prefix);
4808         SendToICS(buf);
4809         if(startedFromSetupPosition || backwardMostMove != 0) {
4810           fen = PositionToFEN(backwardMostMove, NULL);
4811           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4812             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4813             SendToICS(buf);
4814           } else { // FICS: everything has to set by separate bsetup commands
4815             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4816             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4817             SendToICS(buf);
4818             if(!WhiteOnMove(backwardMostMove)) {
4819                 SendToICS("bsetup tomove black\n");
4820             }
4821             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4822             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4823             SendToICS(buf);
4824             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4825             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4826             SendToICS(buf);
4827             i = boards[backwardMostMove][EP_STATUS];
4828             if(i >= 0) { // set e.p.
4829               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4830                 SendToICS(buf);
4831             }
4832             bsetup++;
4833           }
4834         }
4835       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4836             SendToICS("bsetup done\n"); // switch to normal examining.
4837     }
4838     for(i = backwardMostMove; i<last; i++) {
4839         char buf[20];
4840         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4841         SendToICS(buf);
4842     }
4843     SendToICS(ics_prefix);
4844     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4845 }
4846
4847 void
4848 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4849      int rf, ff, rt, ft;
4850      char promoChar;
4851      char move[7];
4852 {
4853     if (rf == DROP_RANK) {
4854       sprintf(move, "%c@%c%c\n",
4855                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4856     } else {
4857         if (promoChar == 'x' || promoChar == NULLCHAR) {
4858           sprintf(move, "%c%c%c%c\n",
4859                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4860         } else {
4861             sprintf(move, "%c%c%c%c%c\n",
4862                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4863         }
4864     }
4865 }
4866
4867 void
4868 ProcessICSInitScript(f)
4869      FILE *f;
4870 {
4871     char buf[MSG_SIZ];
4872
4873     while (fgets(buf, MSG_SIZ, f)) {
4874         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4875     }
4876
4877     fclose(f);
4878 }
4879
4880
4881 static int lastX, lastY, selectFlag, dragging;
4882
4883 void
4884 Sweep(int step)
4885 {
4886     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4887     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4888     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4889     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4890     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4891     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4892     do {
4893         promoSweep -= step;
4894         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4895         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4896         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4897         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4898         if(!step) step = 1;
4899     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4900             appData.testLegality && (promoSweep == king ||
4901             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4902     ChangeDragPiece(promoSweep);
4903 }
4904
4905 int PromoScroll(int x, int y)
4906 {
4907   int step = 0;
4908
4909   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4910   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4911   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4912   if(!step) return FALSE;
4913   lastX = x; lastY = y;
4914   if((promoSweep < BlackPawn) == flipView) step = -step;
4915   if(step > 0) selectFlag = 1;
4916   if(!selectFlag) Sweep(step);
4917   return FALSE;
4918 }
4919
4920 void
4921 NextPiece(int step)
4922 {
4923     ChessSquare piece = boards[currentMove][toY][toX];
4924     do {
4925         pieceSweep -= step;
4926         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4927         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4928         if(!step) step = -1;
4929     } while(PieceToChar(pieceSweep) == '.');
4930     boards[currentMove][toY][toX] = pieceSweep;
4931     DrawPosition(FALSE, boards[currentMove]);
4932     boards[currentMove][toY][toX] = piece;
4933 }
4934 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4935 void
4936 AlphaRank(char *move, int n)
4937 {
4938 //    char *p = move, c; int x, y;
4939
4940     if (appData.debugMode) {
4941         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4942     }
4943
4944     if(move[1]=='*' &&
4945        move[2]>='0' && move[2]<='9' &&
4946        move[3]>='a' && move[3]<='x'    ) {
4947         move[1] = '@';
4948         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4949         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4950     } else
4951     if(move[0]>='0' && move[0]<='9' &&
4952        move[1]>='a' && move[1]<='x' &&
4953        move[2]>='0' && move[2]<='9' &&
4954        move[3]>='a' && move[3]<='x'    ) {
4955         /* input move, Shogi -> normal */
4956         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4957         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4958         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4959         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4960     } else
4961     if(move[1]=='@' &&
4962        move[3]>='0' && move[3]<='9' &&
4963        move[2]>='a' && move[2]<='x'    ) {
4964         move[1] = '*';
4965         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4966         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4967     } else
4968     if(
4969        move[0]>='a' && move[0]<='x' &&
4970        move[3]>='0' && move[3]<='9' &&
4971        move[2]>='a' && move[2]<='x'    ) {
4972          /* output move, normal -> Shogi */
4973         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4974         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4975         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4976         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4977         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4978     }
4979     if (appData.debugMode) {
4980         fprintf(debugFP, "   out = '%s'\n", move);
4981     }
4982 }
4983
4984 char yy_textstr[8000];
4985
4986 /* Parser for moves from gnuchess, ICS, or user typein box */
4987 Boolean
4988 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4989      char *move;
4990      int moveNum;
4991      ChessMove *moveType;
4992      int *fromX, *fromY, *toX, *toY;
4993      char *promoChar;
4994 {
4995     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4996
4997     switch (*moveType) {
4998       case WhitePromotion:
4999       case BlackPromotion:
5000       case WhiteNonPromotion:
5001       case BlackNonPromotion:
5002       case NormalMove:
5003       case WhiteCapturesEnPassant:
5004       case BlackCapturesEnPassant:
5005       case WhiteKingSideCastle:
5006       case WhiteQueenSideCastle:
5007       case BlackKingSideCastle:
5008       case BlackQueenSideCastle:
5009       case WhiteKingSideCastleWild:
5010       case WhiteQueenSideCastleWild:
5011       case BlackKingSideCastleWild:
5012       case BlackQueenSideCastleWild:
5013       /* Code added by Tord: */
5014       case WhiteHSideCastleFR:
5015       case WhiteASideCastleFR:
5016       case BlackHSideCastleFR:
5017       case BlackASideCastleFR:
5018       /* End of code added by Tord */
5019       case IllegalMove:         /* bug or odd chess variant */
5020         *fromX = currentMoveString[0] - AAA;
5021         *fromY = currentMoveString[1] - ONE;
5022         *toX = currentMoveString[2] - AAA;
5023         *toY = currentMoveString[3] - ONE;
5024         *promoChar = currentMoveString[4];
5025         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5026             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5027     if (appData.debugMode) {
5028         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5029     }
5030             *fromX = *fromY = *toX = *toY = 0;
5031             return FALSE;
5032         }
5033         if (appData.testLegality) {
5034           return (*moveType != IllegalMove);
5035         } else {
5036           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5037                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5038         }
5039
5040       case WhiteDrop:
5041       case BlackDrop:
5042         *fromX = *moveType == WhiteDrop ?
5043           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5044           (int) CharToPiece(ToLower(currentMoveString[0]));
5045         *fromY = DROP_RANK;
5046         *toX = currentMoveString[2] - AAA;
5047         *toY = currentMoveString[3] - ONE;
5048         *promoChar = NULLCHAR;
5049         return TRUE;
5050
5051       case AmbiguousMove:
5052       case ImpossibleMove:
5053       case EndOfFile:
5054       case ElapsedTime:
5055       case Comment:
5056       case PGNTag:
5057       case NAG:
5058       case WhiteWins:
5059       case BlackWins:
5060       case GameIsDrawn:
5061       default:
5062     if (appData.debugMode) {
5063         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5064     }
5065         /* bug? */
5066         *fromX = *fromY = *toX = *toY = 0;
5067         *promoChar = NULLCHAR;
5068         return FALSE;
5069     }
5070 }
5071
5072
5073 void
5074 ParsePV(char *pv, Boolean storeComments)
5075 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5076   int fromX, fromY, toX, toY; char promoChar;
5077   ChessMove moveType;
5078   Boolean valid;
5079   int nr = 0;
5080
5081   endPV = forwardMostMove;
5082   do {
5083     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5084     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5085     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5086 if(appData.debugMode){
5087 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);
5088 }
5089     if(!valid && nr == 0 &&
5090        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5091         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5092         // Hande case where played move is different from leading PV move
5093         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5094         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5095         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5096         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5097           endPV += 2; // if position different, keep this
5098           moveList[endPV-1][0] = fromX + AAA;
5099           moveList[endPV-1][1] = fromY + ONE;
5100           moveList[endPV-1][2] = toX + AAA;
5101           moveList[endPV-1][3] = toY + ONE;
5102           parseList[endPV-1][0] = NULLCHAR;
5103           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5104         }
5105       }
5106     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5107     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5108     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5109     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5110         valid++; // allow comments in PV
5111         continue;
5112     }
5113     nr++;
5114     if(endPV+1 > framePtr) break; // no space, truncate
5115     if(!valid) break;
5116     endPV++;
5117     CopyBoard(boards[endPV], boards[endPV-1]);
5118     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5119     moveList[endPV-1][0] = fromX + AAA;
5120     moveList[endPV-1][1] = fromY + ONE;
5121     moveList[endPV-1][2] = toX + AAA;
5122     moveList[endPV-1][3] = toY + ONE;
5123     moveList[endPV-1][4] = promoChar;
5124     moveList[endPV-1][5] = NULLCHAR;
5125     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5126     if(storeComments)
5127         CoordsToAlgebraic(boards[endPV - 1],
5128                              PosFlags(endPV - 1),
5129                              fromY, fromX, toY, toX, promoChar,
5130                              parseList[endPV - 1]);
5131     else
5132         parseList[endPV-1][0] = NULLCHAR;
5133   } while(valid);
5134   currentMove = endPV;
5135   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5136   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5137                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5138   DrawPosition(TRUE, boards[currentMove]);
5139 }
5140
5141 Boolean
5142 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5143 {
5144         int startPV;
5145         char *p;
5146
5147         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5148         lastX = x; lastY = y;
5149         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5150         startPV = index;
5151         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5152         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5153         index = startPV;
5154         do{ while(buf[index] && buf[index] != '\n') index++;
5155         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5156         buf[index] = 0;
5157         ParsePV(buf+startPV, FALSE);
5158         *start = startPV; *end = index-1;
5159         return TRUE;
5160 }
5161
5162 Boolean
5163 LoadPV(int x, int y)
5164 { // called on right mouse click to load PV
5165   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5166   lastX = x; lastY = y;
5167   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5168   return TRUE;
5169 }
5170
5171 void
5172 UnLoadPV()
5173 {
5174   if(endPV < 0) return;
5175   endPV = -1;
5176   currentMove = forwardMostMove;
5177   ClearPremoveHighlights();
5178   DrawPosition(TRUE, boards[currentMove]);
5179 }
5180
5181 void
5182 MovePV(int x, int y, int h)
5183 { // step through PV based on mouse coordinates (called on mouse move)
5184   int margin = h>>3, step = 0;
5185
5186   // we must somehow check if right button is still down (might be released off board!)
5187   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5188   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5189   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5190   if(!step) return;
5191   lastX = x; lastY = y;
5192
5193   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5194   if(endPV < 0) return;
5195   if(y < margin) step = 1; else
5196   if(y > h - margin) step = -1;
5197   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5198   currentMove += step;
5199   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5200   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5201                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5202   DrawPosition(FALSE, boards[currentMove]);
5203 }
5204
5205
5206 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5207 // All positions will have equal probability, but the current method will not provide a unique
5208 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5209 #define DARK 1
5210 #define LITE 2
5211 #define ANY 3
5212
5213 int squaresLeft[4];
5214 int piecesLeft[(int)BlackPawn];
5215 int seed, nrOfShuffles;
5216
5217 void GetPositionNumber()
5218 {       // sets global variable seed
5219         int i;
5220
5221         seed = appData.defaultFrcPosition;
5222         if(seed < 0) { // randomize based on time for negative FRC position numbers
5223                 for(i=0; i<50; i++) seed += random();
5224                 seed = random() ^ random() >> 8 ^ random() << 8;
5225                 if(seed<0) seed = -seed;
5226         }
5227 }
5228
5229 int put(Board board, int pieceType, int rank, int n, int shade)
5230 // put the piece on the (n-1)-th empty squares of the given shade
5231 {
5232         int i;
5233
5234         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5235                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5236                         board[rank][i] = (ChessSquare) pieceType;
5237                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5238                         squaresLeft[ANY]--;
5239                         piecesLeft[pieceType]--;
5240                         return i;
5241                 }
5242         }
5243         return -1;
5244 }
5245
5246
5247 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5248 // calculate where the next piece goes, (any empty square), and put it there
5249 {
5250         int i;
5251
5252         i = seed % squaresLeft[shade];
5253         nrOfShuffles *= squaresLeft[shade];
5254         seed /= squaresLeft[shade];
5255         put(board, pieceType, rank, i, shade);
5256 }
5257
5258 void AddTwoPieces(Board board, int pieceType, int rank)
5259 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5260 {
5261         int i, n=squaresLeft[ANY], j=n-1, k;
5262
5263         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5264         i = seed % k;  // pick one
5265         nrOfShuffles *= k;
5266         seed /= k;
5267         while(i >= j) i -= j--;
5268         j = n - 1 - j; i += j;
5269         put(board, pieceType, rank, j, ANY);
5270         put(board, pieceType, rank, i, ANY);
5271 }
5272
5273 void SetUpShuffle(Board board, int number)
5274 {
5275         int i, p, first=1;
5276
5277         GetPositionNumber(); nrOfShuffles = 1;
5278
5279         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5280         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5281         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5282
5283         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5284
5285         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5286             p = (int) board[0][i];
5287             if(p < (int) BlackPawn) piecesLeft[p] ++;
5288             board[0][i] = EmptySquare;
5289         }
5290
5291         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5292             // shuffles restricted to allow normal castling put KRR first
5293             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5294                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5295             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5296                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5297             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5298                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5299             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5300                 put(board, WhiteRook, 0, 0, ANY);
5301             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5302         }
5303
5304         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5305             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5306             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5307                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5308                 while(piecesLeft[p] >= 2) {
5309                     AddOnePiece(board, p, 0, LITE);
5310                     AddOnePiece(board, p, 0, DARK);
5311                 }
5312                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5313             }
5314
5315         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5316             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5317             // but we leave King and Rooks for last, to possibly obey FRC restriction
5318             if(p == (int)WhiteRook) continue;
5319             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5320             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5321         }
5322
5323         // now everything is placed, except perhaps King (Unicorn) and Rooks
5324
5325         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5326             // Last King gets castling rights
5327             while(piecesLeft[(int)WhiteUnicorn]) {
5328                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5329                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5330             }
5331
5332             while(piecesLeft[(int)WhiteKing]) {
5333                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5334                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5335             }
5336
5337
5338         } else {
5339             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5340             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5341         }
5342
5343         // Only Rooks can be left; simply place them all
5344         while(piecesLeft[(int)WhiteRook]) {
5345                 i = put(board, WhiteRook, 0, 0, ANY);
5346                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5347                         if(first) {
5348                                 first=0;
5349                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5350                         }
5351                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5352                 }
5353         }
5354         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5355             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5356         }
5357
5358         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5359 }
5360
5361 int SetCharTable( char *table, const char * map )
5362 /* [HGM] moved here from winboard.c because of its general usefulness */
5363 /*       Basically a safe strcpy that uses the last character as King */
5364 {
5365     int result = FALSE; int NrPieces;
5366
5367     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5368                     && NrPieces >= 12 && !(NrPieces&1)) {
5369         int i; /* [HGM] Accept even length from 12 to 34 */
5370
5371         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5372         for( i=0; i<NrPieces/2-1; i++ ) {
5373             table[i] = map[i];
5374             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5375         }
5376         table[(int) WhiteKing]  = map[NrPieces/2-1];
5377         table[(int) BlackKing]  = map[NrPieces-1];
5378
5379         result = TRUE;
5380     }
5381
5382     return result;
5383 }
5384
5385 void Prelude(Board board)
5386 {       // [HGM] superchess: random selection of exo-pieces
5387         int i, j, k; ChessSquare p;
5388         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5389
5390         GetPositionNumber(); // use FRC position number
5391
5392         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5393             SetCharTable(pieceToChar, appData.pieceToCharTable);
5394             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5395                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5396         }
5397
5398         j = seed%4;                 seed /= 4;
5399         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5400         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5401         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5402         j = seed%3 + (seed%3 >= j); seed /= 3;
5403         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5404         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5405         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5406         j = seed%3;                 seed /= 3;
5407         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5408         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5409         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5410         j = seed%2 + (seed%2 >= j); seed /= 2;
5411         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5412         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5413         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5414         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5415         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5416         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5417         put(board, exoPieces[0],    0, 0, ANY);
5418         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5419 }
5420
5421 void
5422 InitPosition(redraw)
5423      int redraw;
5424 {
5425     ChessSquare (* pieces)[BOARD_FILES];
5426     int i, j, pawnRow, overrule,
5427     oldx = gameInfo.boardWidth,
5428     oldy = gameInfo.boardHeight,
5429     oldh = gameInfo.holdingsWidth;
5430     static int oldv;
5431
5432     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5433
5434     /* [AS] Initialize pv info list [HGM] and game status */
5435     {
5436         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5437             pvInfoList[i].depth = 0;
5438             boards[i][EP_STATUS] = EP_NONE;
5439             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5440         }
5441
5442         initialRulePlies = 0; /* 50-move counter start */
5443
5444         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5445         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5446     }
5447
5448
5449     /* [HGM] logic here is completely changed. In stead of full positions */
5450     /* the initialized data only consist of the two backranks. The switch */
5451     /* selects which one we will use, which is than copied to the Board   */
5452     /* initialPosition, which for the rest is initialized by Pawns and    */
5453     /* empty squares. This initial position is then copied to boards[0],  */
5454     /* possibly after shuffling, so that it remains available.            */
5455
5456     gameInfo.holdingsWidth = 0; /* default board sizes */
5457     gameInfo.boardWidth    = 8;
5458     gameInfo.boardHeight   = 8;
5459     gameInfo.holdingsSize  = 0;
5460     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5461     for(i=0; i<BOARD_FILES-2; i++)
5462       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5463     initialPosition[EP_STATUS] = EP_NONE;
5464     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5465     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5466          SetCharTable(pieceNickName, appData.pieceNickNames);
5467     else SetCharTable(pieceNickName, "............");
5468     pieces = FIDEArray;
5469
5470     switch (gameInfo.variant) {
5471     case VariantFischeRandom:
5472       shuffleOpenings = TRUE;
5473     default:
5474       break;
5475     case VariantShatranj:
5476       pieces = ShatranjArray;
5477       nrCastlingRights = 0;
5478       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5479       break;
5480     case VariantMakruk:
5481       pieces = makrukArray;
5482       nrCastlingRights = 0;
5483       startedFromSetupPosition = TRUE;
5484       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5485       break;
5486     case VariantTwoKings:
5487       pieces = twoKingsArray;
5488       break;
5489     case VariantCapaRandom:
5490       shuffleOpenings = TRUE;
5491     case VariantCapablanca:
5492       pieces = CapablancaArray;
5493       gameInfo.boardWidth = 10;
5494       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5495       break;
5496     case VariantGothic:
5497       pieces = GothicArray;
5498       gameInfo.boardWidth = 10;
5499       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5500       break;
5501     case VariantSChess:
5502       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5503       gameInfo.holdingsSize = 7;
5504       break;
5505     case VariantJanus:
5506       pieces = JanusArray;
5507       gameInfo.boardWidth = 10;
5508       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5509       nrCastlingRights = 6;
5510         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5511         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5512         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5513         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5514         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5515         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5516       break;
5517     case VariantFalcon:
5518       pieces = FalconArray;
5519       gameInfo.boardWidth = 10;
5520       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5521       break;
5522     case VariantXiangqi:
5523       pieces = XiangqiArray;
5524       gameInfo.boardWidth  = 9;
5525       gameInfo.boardHeight = 10;
5526       nrCastlingRights = 0;
5527       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5528       break;
5529     case VariantShogi:
5530       pieces = ShogiArray;
5531       gameInfo.boardWidth  = 9;
5532       gameInfo.boardHeight = 9;
5533       gameInfo.holdingsSize = 7;
5534       nrCastlingRights = 0;
5535       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5536       break;
5537     case VariantCourier:
5538       pieces = CourierArray;
5539       gameInfo.boardWidth  = 12;
5540       nrCastlingRights = 0;
5541       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5542       break;
5543     case VariantKnightmate:
5544       pieces = KnightmateArray;
5545       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5546       break;
5547     case VariantSpartan:
5548       pieces = SpartanArray;
5549       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5550       break;
5551     case VariantFairy:
5552       pieces = fairyArray;
5553       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5554       break;
5555     case VariantGreat:
5556       pieces = GreatArray;
5557       gameInfo.boardWidth = 10;
5558       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5559       gameInfo.holdingsSize = 8;
5560       break;
5561     case VariantSuper:
5562       pieces = FIDEArray;
5563       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5564       gameInfo.holdingsSize = 8;
5565       startedFromSetupPosition = TRUE;
5566       break;
5567     case VariantCrazyhouse:
5568     case VariantBughouse:
5569       pieces = FIDEArray;
5570       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5571       gameInfo.holdingsSize = 5;
5572       break;
5573     case VariantWildCastle:
5574       pieces = FIDEArray;
5575       /* !!?shuffle with kings guaranteed to be on d or e file */
5576       shuffleOpenings = 1;
5577       break;
5578     case VariantNoCastle:
5579       pieces = FIDEArray;
5580       nrCastlingRights = 0;
5581       /* !!?unconstrained back-rank shuffle */
5582       shuffleOpenings = 1;
5583       break;
5584     }
5585
5586     overrule = 0;
5587     if(appData.NrFiles >= 0) {
5588         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5589         gameInfo.boardWidth = appData.NrFiles;
5590     }
5591     if(appData.NrRanks >= 0) {
5592         gameInfo.boardHeight = appData.NrRanks;
5593     }
5594     if(appData.holdingsSize >= 0) {
5595         i = appData.holdingsSize;
5596         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5597         gameInfo.holdingsSize = i;
5598     }
5599     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5600     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5601         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5602
5603     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5604     if(pawnRow < 1) pawnRow = 1;
5605     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5606
5607     /* User pieceToChar list overrules defaults */
5608     if(appData.pieceToCharTable != NULL)
5609         SetCharTable(pieceToChar, appData.pieceToCharTable);
5610
5611     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5612
5613         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5614             s = (ChessSquare) 0; /* account holding counts in guard band */
5615         for( i=0; i<BOARD_HEIGHT; i++ )
5616             initialPosition[i][j] = s;
5617
5618         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5619         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5620         initialPosition[pawnRow][j] = WhitePawn;
5621         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5622         if(gameInfo.variant == VariantXiangqi) {
5623             if(j&1) {
5624                 initialPosition[pawnRow][j] =
5625                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5626                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5627                    initialPosition[2][j] = WhiteCannon;
5628                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5629                 }
5630             }
5631         }
5632         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5633     }
5634     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5635
5636             j=BOARD_LEFT+1;
5637             initialPosition[1][j] = WhiteBishop;
5638             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5639             j=BOARD_RGHT-2;
5640             initialPosition[1][j] = WhiteRook;
5641             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5642     }
5643
5644     if( nrCastlingRights == -1) {
5645         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5646         /*       This sets default castling rights from none to normal corners   */
5647         /* Variants with other castling rights must set them themselves above    */
5648         nrCastlingRights = 6;
5649
5650         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5651         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5652         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5653         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5654         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5655         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5656      }
5657
5658      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5659      if(gameInfo.variant == VariantGreat) { // promotion commoners
5660         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5661         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5662         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5663         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5664      }
5665      if( gameInfo.variant == VariantSChess ) {
5666       initialPosition[1][0] = BlackMarshall;
5667       initialPosition[2][0] = BlackAngel;
5668       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5669       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5670       initialPosition[1][1] = initialPosition[2][1] = 
5671       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5672      }
5673   if (appData.debugMode) {
5674     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5675   }
5676     if(shuffleOpenings) {
5677         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5678         startedFromSetupPosition = TRUE;
5679     }
5680     if(startedFromPositionFile) {
5681       /* [HGM] loadPos: use PositionFile for every new game */
5682       CopyBoard(initialPosition, filePosition);
5683       for(i=0; i<nrCastlingRights; i++)
5684           initialRights[i] = filePosition[CASTLING][i];
5685       startedFromSetupPosition = TRUE;
5686     }
5687
5688     CopyBoard(boards[0], initialPosition);
5689
5690     if(oldx != gameInfo.boardWidth ||
5691        oldy != gameInfo.boardHeight ||
5692        oldv != gameInfo.variant ||
5693        oldh != gameInfo.holdingsWidth
5694                                          )
5695             InitDrawingSizes(-2 ,0);
5696
5697     oldv = gameInfo.variant;
5698     if (redraw)
5699       DrawPosition(TRUE, boards[currentMove]);
5700 }
5701
5702 void
5703 SendBoard(cps, moveNum)
5704      ChessProgramState *cps;
5705      int moveNum;
5706 {
5707     char message[MSG_SIZ];
5708
5709     if (cps->useSetboard) {
5710       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5711       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5712       SendToProgram(message, cps);
5713       free(fen);
5714
5715     } else {
5716       ChessSquare *bp;
5717       int i, j;
5718       /* Kludge to set black to move, avoiding the troublesome and now
5719        * deprecated "black" command.
5720        */
5721       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5722         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5723
5724       SendToProgram("edit\n", cps);
5725       SendToProgram("#\n", cps);
5726       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5727         bp = &boards[moveNum][i][BOARD_LEFT];
5728         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5729           if ((int) *bp < (int) BlackPawn) {
5730             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5731                     AAA + j, ONE + i);
5732             if(message[0] == '+' || message[0] == '~') {
5733               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5734                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5735                         AAA + j, ONE + i);
5736             }
5737             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5738                 message[1] = BOARD_RGHT   - 1 - j + '1';
5739                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5740             }
5741             SendToProgram(message, cps);
5742           }
5743         }
5744       }
5745
5746       SendToProgram("c\n", cps);
5747       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5748         bp = &boards[moveNum][i][BOARD_LEFT];
5749         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5750           if (((int) *bp != (int) EmptySquare)
5751               && ((int) *bp >= (int) BlackPawn)) {
5752             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5753                     AAA + j, ONE + i);
5754             if(message[0] == '+' || message[0] == '~') {
5755               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5756                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5757                         AAA + j, ONE + i);
5758             }
5759             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5760                 message[1] = BOARD_RGHT   - 1 - j + '1';
5761                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5762             }
5763             SendToProgram(message, cps);
5764           }
5765         }
5766       }
5767
5768       SendToProgram(".\n", cps);
5769     }
5770     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5771 }
5772
5773 ChessSquare
5774 DefaultPromoChoice(int white)
5775 {
5776     ChessSquare result;
5777     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5778         result = WhiteFerz; // no choice
5779     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5780         result= WhiteKing; // in Suicide Q is the last thing we want
5781     else if(gameInfo.variant == VariantSpartan)
5782         result = white ? WhiteQueen : WhiteAngel;
5783     else result = WhiteQueen;
5784     if(!white) result = WHITE_TO_BLACK result;
5785     return result;
5786 }
5787
5788 static int autoQueen; // [HGM] oneclick
5789
5790 int
5791 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5792 {
5793     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5794     /* [HGM] add Shogi promotions */
5795     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5796     ChessSquare piece;
5797     ChessMove moveType;
5798     Boolean premove;
5799
5800     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5801     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5802
5803     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5804       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5805         return FALSE;
5806
5807     piece = boards[currentMove][fromY][fromX];
5808     if(gameInfo.variant == VariantShogi) {
5809         promotionZoneSize = BOARD_HEIGHT/3;
5810         highestPromotingPiece = (int)WhiteFerz;
5811     } else if(gameInfo.variant == VariantMakruk) {
5812         promotionZoneSize = 3;
5813     }
5814
5815     // Treat Lance as Pawn when it is not representing Amazon
5816     if(gameInfo.variant != VariantSuper) {
5817         if(piece == WhiteLance) piece = WhitePawn; else
5818         if(piece == BlackLance) piece = BlackPawn;
5819     }
5820
5821     // next weed out all moves that do not touch the promotion zone at all
5822     if((int)piece >= BlackPawn) {
5823         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5824              return FALSE;
5825         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5826     } else {
5827         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5828            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5829     }
5830
5831     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5832
5833     // weed out mandatory Shogi promotions
5834     if(gameInfo.variant == VariantShogi) {
5835         if(piece >= BlackPawn) {
5836             if(toY == 0 && piece == BlackPawn ||
5837                toY == 0 && piece == BlackQueen ||
5838                toY <= 1 && piece == BlackKnight) {
5839                 *promoChoice = '+';
5840                 return FALSE;
5841             }
5842         } else {
5843             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5844                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5845                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5846                 *promoChoice = '+';
5847                 return FALSE;
5848             }
5849         }
5850     }
5851
5852     // weed out obviously illegal Pawn moves
5853     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5854         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5855         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5856         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5857         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5858         // note we are not allowed to test for valid (non-)capture, due to premove
5859     }
5860
5861     // we either have a choice what to promote to, or (in Shogi) whether to promote
5862     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5863         *promoChoice = PieceToChar(BlackFerz);  // no choice
5864         return FALSE;
5865     }
5866     // no sense asking what we must promote to if it is going to explode...
5867     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5868         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5869         return FALSE;
5870     }
5871     // give caller the default choice even if we will not make it
5872     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5873     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5874     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5875                            && gameInfo.variant != VariantShogi
5876                            && gameInfo.variant != VariantSuper) return FALSE;
5877     if(autoQueen) return FALSE; // predetermined
5878
5879     // suppress promotion popup on illegal moves that are not premoves
5880     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5881               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5882     if(appData.testLegality && !premove) {
5883         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5884                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5885         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5886             return FALSE;
5887     }
5888
5889     return TRUE;
5890 }
5891
5892 int
5893 InPalace(row, column)
5894      int row, column;
5895 {   /* [HGM] for Xiangqi */
5896     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5897          column < (BOARD_WIDTH + 4)/2 &&
5898          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5899     return FALSE;
5900 }
5901
5902 int
5903 PieceForSquare (x, y)
5904      int x;
5905      int y;
5906 {
5907   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5908      return -1;
5909   else
5910      return boards[currentMove][y][x];
5911 }
5912
5913 int
5914 OKToStartUserMove(x, y)
5915      int x, y;
5916 {
5917     ChessSquare from_piece;
5918     int white_piece;
5919
5920     if (matchMode) return FALSE;
5921     if (gameMode == EditPosition) return TRUE;
5922
5923     if (x >= 0 && y >= 0)
5924       from_piece = boards[currentMove][y][x];
5925     else
5926       from_piece = EmptySquare;
5927
5928     if (from_piece == EmptySquare) return FALSE;
5929
5930     white_piece = (int)from_piece >= (int)WhitePawn &&
5931       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5932
5933     switch (gameMode) {
5934       case PlayFromGameFile:
5935       case AnalyzeFile:
5936       case TwoMachinesPlay:
5937       case EndOfGame:
5938         return FALSE;
5939
5940       case IcsObserving:
5941       case IcsIdle:
5942         return FALSE;
5943
5944       case MachinePlaysWhite:
5945       case IcsPlayingBlack:
5946         if (appData.zippyPlay) return FALSE;
5947         if (white_piece) {
5948             DisplayMoveError(_("You are playing Black"));
5949             return FALSE;
5950         }
5951         break;
5952
5953       case MachinePlaysBlack:
5954       case IcsPlayingWhite:
5955         if (appData.zippyPlay) return FALSE;
5956         if (!white_piece) {
5957             DisplayMoveError(_("You are playing White"));
5958             return FALSE;
5959         }
5960         break;
5961
5962       case EditGame:
5963         if (!white_piece && WhiteOnMove(currentMove)) {
5964             DisplayMoveError(_("It is White's turn"));
5965             return FALSE;
5966         }
5967         if (white_piece && !WhiteOnMove(currentMove)) {
5968             DisplayMoveError(_("It is Black's turn"));
5969             return FALSE;
5970         }
5971         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5972             /* Editing correspondence game history */
5973             /* Could disallow this or prompt for confirmation */
5974             cmailOldMove = -1;
5975         }
5976         break;
5977
5978       case BeginningOfGame:
5979         if (appData.icsActive) return FALSE;
5980         if (!appData.noChessProgram) {
5981             if (!white_piece) {
5982                 DisplayMoveError(_("You are playing White"));
5983                 return FALSE;
5984             }
5985         }
5986         break;
5987
5988       case Training:
5989         if (!white_piece && WhiteOnMove(currentMove)) {
5990             DisplayMoveError(_("It is White's turn"));
5991             return FALSE;
5992         }
5993         if (white_piece && !WhiteOnMove(currentMove)) {
5994             DisplayMoveError(_("It is Black's turn"));
5995             return FALSE;
5996         }
5997         break;
5998
5999       default:
6000       case IcsExamining:
6001         break;
6002     }
6003     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6004         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6005         && gameMode != AnalyzeFile && gameMode != Training) {
6006         DisplayMoveError(_("Displayed position is not current"));
6007         return FALSE;
6008     }
6009     return TRUE;
6010 }
6011
6012 Boolean
6013 OnlyMove(int *x, int *y, Boolean captures) {
6014     DisambiguateClosure cl;
6015     if (appData.zippyPlay) return FALSE;
6016     switch(gameMode) {
6017       case MachinePlaysBlack:
6018       case IcsPlayingWhite:
6019       case BeginningOfGame:
6020         if(!WhiteOnMove(currentMove)) return FALSE;
6021         break;
6022       case MachinePlaysWhite:
6023       case IcsPlayingBlack:
6024         if(WhiteOnMove(currentMove)) return FALSE;
6025         break;
6026       case EditGame:
6027         break;
6028       default:
6029         return FALSE;
6030     }
6031     cl.pieceIn = EmptySquare;
6032     cl.rfIn = *y;
6033     cl.ffIn = *x;
6034     cl.rtIn = -1;
6035     cl.ftIn = -1;
6036     cl.promoCharIn = NULLCHAR;
6037     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6038     if( cl.kind == NormalMove ||
6039         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6040         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6041         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6042       fromX = cl.ff;
6043       fromY = cl.rf;
6044       *x = cl.ft;
6045       *y = cl.rt;
6046       return TRUE;
6047     }
6048     if(cl.kind != ImpossibleMove) return FALSE;
6049     cl.pieceIn = EmptySquare;
6050     cl.rfIn = -1;
6051     cl.ffIn = -1;
6052     cl.rtIn = *y;
6053     cl.ftIn = *x;
6054     cl.promoCharIn = NULLCHAR;
6055     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6056     if( cl.kind == NormalMove ||
6057         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6058         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6059         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6060       fromX = cl.ff;
6061       fromY = cl.rf;
6062       *x = cl.ft;
6063       *y = cl.rt;
6064       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6065       return TRUE;
6066     }
6067     return FALSE;
6068 }
6069
6070 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6071 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6072 int lastLoadGameUseList = FALSE;
6073 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6074 ChessMove lastLoadGameStart = EndOfFile;
6075
6076 void
6077 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6078      int fromX, fromY, toX, toY;
6079      int promoChar;
6080 {
6081     ChessMove moveType;
6082     ChessSquare pdown, pup;
6083
6084     /* Check if the user is playing in turn.  This is complicated because we
6085        let the user "pick up" a piece before it is his turn.  So the piece he
6086        tried to pick up may have been captured by the time he puts it down!
6087        Therefore we use the color the user is supposed to be playing in this
6088        test, not the color of the piece that is currently on the starting
6089        square---except in EditGame mode, where the user is playing both
6090        sides; fortunately there the capture race can't happen.  (It can
6091        now happen in IcsExamining mode, but that's just too bad.  The user
6092        will get a somewhat confusing message in that case.)
6093        */
6094
6095     switch (gameMode) {
6096       case PlayFromGameFile:
6097       case AnalyzeFile:
6098       case TwoMachinesPlay:
6099       case EndOfGame:
6100       case IcsObserving:
6101       case IcsIdle:
6102         /* We switched into a game mode where moves are not accepted,
6103            perhaps while the mouse button was down. */
6104         return;
6105
6106       case MachinePlaysWhite:
6107         /* User is moving for Black */
6108         if (WhiteOnMove(currentMove)) {
6109             DisplayMoveError(_("It is White's turn"));
6110             return;
6111         }
6112         break;
6113
6114       case MachinePlaysBlack:
6115         /* User is moving for White */
6116         if (!WhiteOnMove(currentMove)) {
6117             DisplayMoveError(_("It is Black's turn"));
6118             return;
6119         }
6120         break;
6121
6122       case EditGame:
6123       case IcsExamining:
6124       case BeginningOfGame:
6125       case AnalyzeMode:
6126       case Training:
6127         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6128         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6129             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6130             /* User is moving for Black */
6131             if (WhiteOnMove(currentMove)) {
6132                 DisplayMoveError(_("It is White's turn"));
6133                 return;
6134             }
6135         } else {
6136             /* User is moving for White */
6137             if (!WhiteOnMove(currentMove)) {
6138                 DisplayMoveError(_("It is Black's turn"));
6139                 return;
6140             }
6141         }
6142         break;
6143
6144       case IcsPlayingBlack:
6145         /* User is moving for Black */
6146         if (WhiteOnMove(currentMove)) {
6147             if (!appData.premove) {
6148                 DisplayMoveError(_("It is White's turn"));
6149             } else if (toX >= 0 && toY >= 0) {
6150                 premoveToX = toX;
6151                 premoveToY = toY;
6152                 premoveFromX = fromX;
6153                 premoveFromY = fromY;
6154                 premovePromoChar = promoChar;
6155                 gotPremove = 1;
6156                 if (appData.debugMode)
6157                     fprintf(debugFP, "Got premove: fromX %d,"
6158                             "fromY %d, toX %d, toY %d\n",
6159                             fromX, fromY, toX, toY);
6160             }
6161             return;
6162         }
6163         break;
6164
6165       case IcsPlayingWhite:
6166         /* User is moving for White */
6167         if (!WhiteOnMove(currentMove)) {
6168             if (!appData.premove) {
6169                 DisplayMoveError(_("It is Black's turn"));
6170             } else if (toX >= 0 && toY >= 0) {
6171                 premoveToX = toX;
6172                 premoveToY = toY;
6173                 premoveFromX = fromX;
6174                 premoveFromY = fromY;
6175                 premovePromoChar = promoChar;
6176                 gotPremove = 1;
6177                 if (appData.debugMode)
6178                     fprintf(debugFP, "Got premove: fromX %d,"
6179                             "fromY %d, toX %d, toY %d\n",
6180                             fromX, fromY, toX, toY);
6181             }
6182             return;
6183         }
6184         break;
6185
6186       default:
6187         break;
6188
6189       case EditPosition:
6190         /* EditPosition, empty square, or different color piece;
6191            click-click move is possible */
6192         if (toX == -2 || toY == -2) {
6193             boards[0][fromY][fromX] = EmptySquare;
6194             DrawPosition(FALSE, boards[currentMove]);
6195             return;
6196         } else if (toX >= 0 && toY >= 0) {
6197             boards[0][toY][toX] = boards[0][fromY][fromX];
6198             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6199                 if(boards[0][fromY][0] != EmptySquare) {
6200                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6201                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6202                 }
6203             } else
6204             if(fromX == BOARD_RGHT+1) {
6205                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6206                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6207                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6208                 }
6209             } else
6210             boards[0][fromY][fromX] = EmptySquare;
6211             DrawPosition(FALSE, boards[currentMove]);
6212             return;
6213         }
6214         return;
6215     }
6216
6217     if(toX < 0 || toY < 0) return;
6218     pdown = boards[currentMove][fromY][fromX];
6219     pup = boards[currentMove][toY][toX];
6220
6221     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6222     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6223          if( pup != EmptySquare ) return;
6224          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6225            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6226                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6227            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6228            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6229            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6230            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6231          fromY = DROP_RANK;
6232     }
6233
6234     /* [HGM] always test for legality, to get promotion info */
6235     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6236                                          fromY, fromX, toY, toX, promoChar);
6237     /* [HGM] but possibly ignore an IllegalMove result */
6238     if (appData.testLegality) {
6239         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6240             DisplayMoveError(_("Illegal move"));
6241             return;
6242         }
6243     }
6244
6245     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6246 }
6247
6248 /* Common tail of UserMoveEvent and DropMenuEvent */
6249 int
6250 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6251      ChessMove moveType;
6252      int fromX, fromY, toX, toY;
6253      /*char*/int promoChar;
6254 {
6255     char *bookHit = 0;
6256
6257     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6258         // [HGM] superchess: suppress promotions to non-available piece
6259         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6260         if(WhiteOnMove(currentMove)) {
6261             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6262         } else {
6263             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6264         }
6265     }
6266
6267     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6268        move type in caller when we know the move is a legal promotion */
6269     if(moveType == NormalMove && promoChar)
6270         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6271
6272     /* [HGM] <popupFix> The following if has been moved here from
6273        UserMoveEvent(). Because it seemed to belong here (why not allow
6274        piece drops in training games?), and because it can only be
6275        performed after it is known to what we promote. */
6276     if (gameMode == Training) {
6277       /* compare the move played on the board to the next move in the
6278        * game. If they match, display the move and the opponent's response.
6279        * If they don't match, display an error message.
6280        */
6281       int saveAnimate;
6282       Board testBoard;
6283       CopyBoard(testBoard, boards[currentMove]);
6284       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6285
6286       if (CompareBoards(testBoard, boards[currentMove+1])) {
6287         ForwardInner(currentMove+1);
6288
6289         /* Autoplay the opponent's response.
6290          * if appData.animate was TRUE when Training mode was entered,
6291          * the response will be animated.
6292          */
6293         saveAnimate = appData.animate;
6294         appData.animate = animateTraining;
6295         ForwardInner(currentMove+1);
6296         appData.animate = saveAnimate;
6297
6298         /* check for the end of the game */
6299         if (currentMove >= forwardMostMove) {
6300           gameMode = PlayFromGameFile;
6301           ModeHighlight();
6302           SetTrainingModeOff();
6303           DisplayInformation(_("End of game"));
6304         }
6305       } else {
6306         DisplayError(_("Incorrect move"), 0);
6307       }
6308       return 1;
6309     }
6310
6311   /* Ok, now we know that the move is good, so we can kill
6312      the previous line in Analysis Mode */
6313   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6314                                 && currentMove < forwardMostMove) {
6315     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6316     else forwardMostMove = currentMove;
6317   }
6318
6319   /* If we need the chess program but it's dead, restart it */
6320   ResurrectChessProgram();
6321
6322   /* A user move restarts a paused game*/
6323   if (pausing)
6324     PauseEvent();
6325
6326   thinkOutput[0] = NULLCHAR;
6327
6328   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6329
6330   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6331     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6332     return 1;
6333   }
6334
6335   if (gameMode == BeginningOfGame) {
6336     if (appData.noChessProgram) {
6337       gameMode = EditGame;
6338       SetGameInfo();
6339     } else {
6340       char buf[MSG_SIZ];
6341       gameMode = MachinePlaysBlack;
6342       StartClocks();
6343       SetGameInfo();
6344       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6345       DisplayTitle(buf);
6346       if (first.sendName) {
6347         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6348         SendToProgram(buf, &first);
6349       }
6350       StartClocks();
6351     }
6352     ModeHighlight();
6353   }
6354
6355   /* Relay move to ICS or chess engine */
6356   if (appData.icsActive) {
6357     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6358         gameMode == IcsExamining) {
6359       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6360         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6361         SendToICS("draw ");
6362         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6363       }
6364       // also send plain move, in case ICS does not understand atomic claims
6365       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6366       ics_user_moved = 1;
6367     }
6368   } else {
6369     if (first.sendTime && (gameMode == BeginningOfGame ||
6370                            gameMode == MachinePlaysWhite ||
6371                            gameMode == MachinePlaysBlack)) {
6372       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6373     }
6374     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6375          // [HGM] book: if program might be playing, let it use book
6376         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6377         first.maybeThinking = TRUE;
6378     } else SendMoveToProgram(forwardMostMove-1, &first);
6379     if (currentMove == cmailOldMove + 1) {
6380       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6381     }
6382   }
6383
6384   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6385
6386   switch (gameMode) {
6387   case EditGame:
6388     if(appData.testLegality)
6389     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6390     case MT_NONE:
6391     case MT_CHECK:
6392       break;
6393     case MT_CHECKMATE:
6394     case MT_STAINMATE:
6395       if (WhiteOnMove(currentMove)) {
6396         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6397       } else {
6398         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6399       }
6400       break;
6401     case MT_STALEMATE:
6402       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6403       break;
6404     }
6405     break;
6406
6407   case MachinePlaysBlack:
6408   case MachinePlaysWhite:
6409     /* disable certain menu options while machine is thinking */
6410     SetMachineThinkingEnables();
6411     break;
6412
6413   default:
6414     break;
6415   }
6416
6417   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6418   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6419
6420   if(bookHit) { // [HGM] book: simulate book reply
6421         static char bookMove[MSG_SIZ]; // a bit generous?
6422
6423         programStats.nodes = programStats.depth = programStats.time =
6424         programStats.score = programStats.got_only_move = 0;
6425         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6426
6427         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6428         strcat(bookMove, bookHit);
6429         HandleMachineMove(bookMove, &first);
6430   }
6431   return 1;
6432 }
6433
6434 void
6435 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6436      Board board;
6437      int flags;
6438      ChessMove kind;
6439      int rf, ff, rt, ft;
6440      VOIDSTAR closure;
6441 {
6442     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6443     Markers *m = (Markers *) closure;
6444     if(rf == fromY && ff == fromX)
6445         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6446                          || kind == WhiteCapturesEnPassant
6447                          || kind == BlackCapturesEnPassant);
6448     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6449 }
6450
6451 void
6452 MarkTargetSquares(int clear)
6453 {
6454   int x, y;
6455   if(!appData.markers || !appData.highlightDragging ||
6456      !appData.testLegality || gameMode == EditPosition) return;
6457   if(clear) {
6458     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6459   } else {
6460     int capt = 0;
6461     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6462     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6463       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6464       if(capt)
6465       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6466     }
6467   }
6468   DrawPosition(TRUE, NULL);
6469 }
6470
6471 int
6472 Explode(Board board, int fromX, int fromY, int toX, int toY)
6473 {
6474     if(gameInfo.variant == VariantAtomic &&
6475        (board[toY][toX] != EmptySquare ||                     // capture?
6476         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6477                          board[fromY][fromX] == BlackPawn   )
6478       )) {
6479         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6480         return TRUE;
6481     }
6482     return FALSE;
6483 }
6484
6485 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6486
6487 int CanPromote(ChessSquare piece, int y)
6488 {
6489         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6490         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6491         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6492            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6493            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6494                                                   gameInfo.variant == VariantMakruk) return FALSE;
6495         return (piece == BlackPawn && y == 1 ||
6496                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6497                 piece == BlackLance && y == 1 ||
6498                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6499 }
6500
6501 void LeftClick(ClickType clickType, int xPix, int yPix)
6502 {
6503     int x, y;
6504     Boolean saveAnimate;
6505     static int second = 0, promotionChoice = 0, clearFlag = 0;
6506     char promoChoice = NULLCHAR;
6507     ChessSquare piece;
6508
6509     if(appData.seekGraph && appData.icsActive && loggedOn &&
6510         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6511         SeekGraphClick(clickType, xPix, yPix, 0);
6512         return;
6513     }
6514
6515     if (clickType == Press) ErrorPopDown();
6516     MarkTargetSquares(1);
6517
6518     x = EventToSquare(xPix, BOARD_WIDTH);
6519     y = EventToSquare(yPix, BOARD_HEIGHT);
6520     if (!flipView && y >= 0) {
6521         y = BOARD_HEIGHT - 1 - y;
6522     }
6523     if (flipView && x >= 0) {
6524         x = BOARD_WIDTH - 1 - x;
6525     }
6526
6527     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6528         defaultPromoChoice = promoSweep;
6529         promoSweep = EmptySquare;   // terminate sweep
6530         promoDefaultAltered = TRUE;
6531         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6532     }
6533
6534     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6535         if(clickType == Release) return; // ignore upclick of click-click destination
6536         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6537         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6538         if(gameInfo.holdingsWidth &&
6539                 (WhiteOnMove(currentMove)
6540                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6541                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6542             // click in right holdings, for determining promotion piece
6543             ChessSquare p = boards[currentMove][y][x];
6544             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6545             if(p != EmptySquare) {
6546                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6547                 fromX = fromY = -1;
6548                 return;
6549             }
6550         }
6551         DrawPosition(FALSE, boards[currentMove]);
6552         return;
6553     }
6554
6555     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6556     if(clickType == Press
6557             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6558               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6559               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6560         return;
6561
6562     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6563         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6564
6565     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6566         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6567                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6568         defaultPromoChoice = DefaultPromoChoice(side);
6569     }
6570
6571     autoQueen = appData.alwaysPromoteToQueen;
6572
6573     if (fromX == -1) {
6574       int originalY = y;
6575       gatingPiece = EmptySquare;
6576       if (clickType != Press) {
6577         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6578             DragPieceEnd(xPix, yPix); dragging = 0;
6579             DrawPosition(FALSE, NULL);
6580         }
6581         return;
6582       }
6583       fromX = x; fromY = y;
6584       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6585          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6586          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6587             /* First square */
6588             if (OKToStartUserMove(fromX, fromY)) {
6589                 second = 0;
6590                 MarkTargetSquares(0);
6591                 DragPieceBegin(xPix, yPix); dragging = 1;
6592                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6593                     promoSweep = defaultPromoChoice;
6594                     selectFlag = 0; lastX = xPix; lastY = yPix;
6595                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6596                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6597                 }
6598                 if (appData.highlightDragging) {
6599                     SetHighlights(fromX, fromY, -1, -1);
6600                 }
6601             } else fromX = fromY = -1;
6602             return;
6603         }
6604     }
6605
6606     /* fromX != -1 */
6607     if (clickType == Press && gameMode != EditPosition) {
6608         ChessSquare fromP;
6609         ChessSquare toP;
6610         int frc;
6611
6612         // ignore off-board to clicks
6613         if(y < 0 || x < 0) return;
6614
6615         /* Check if clicking again on the same color piece */
6616         fromP = boards[currentMove][fromY][fromX];
6617         toP = boards[currentMove][y][x];
6618         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6619         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6620              WhitePawn <= toP && toP <= WhiteKing &&
6621              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6622              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6623             (BlackPawn <= fromP && fromP <= BlackKing &&
6624              BlackPawn <= toP && toP <= BlackKing &&
6625              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6626              !(fromP == BlackKing && toP == BlackRook && frc))) {
6627             /* Clicked again on same color piece -- changed his mind */
6628             second = (x == fromX && y == fromY);
6629             promoDefaultAltered = FALSE;
6630            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6631             if (appData.highlightDragging) {
6632                 SetHighlights(x, y, -1, -1);
6633             } else {
6634                 ClearHighlights();
6635             }
6636             if (OKToStartUserMove(x, y)) {
6637                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6638                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6639                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6640                  gatingPiece = boards[currentMove][fromY][fromX];
6641                 else gatingPiece = EmptySquare;
6642                 fromX = x;
6643                 fromY = y; dragging = 1;
6644                 MarkTargetSquares(0);
6645                 DragPieceBegin(xPix, yPix);
6646                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6647                     promoSweep = defaultPromoChoice;
6648                     selectFlag = 0; lastX = xPix; lastY = yPix;
6649                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6650                 }
6651             }
6652            }
6653            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6654            second = FALSE; 
6655         }
6656         // ignore clicks on holdings
6657         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6658     }
6659
6660     if (clickType == Release && x == fromX && y == fromY) {
6661         DragPieceEnd(xPix, yPix); dragging = 0;
6662         if(clearFlag) {
6663             // a deferred attempt to click-click move an empty square on top of a piece
6664             boards[currentMove][y][x] = EmptySquare;
6665             ClearHighlights();
6666             DrawPosition(FALSE, boards[currentMove]);
6667             fromX = fromY = -1; clearFlag = 0;
6668             return;
6669         }
6670         if (appData.animateDragging) {
6671             /* Undo animation damage if any */
6672             DrawPosition(FALSE, NULL);
6673         }
6674         if (second) {
6675             /* Second up/down in same square; just abort move */
6676             second = 0;
6677             fromX = fromY = -1;
6678             gatingPiece = EmptySquare;
6679             ClearHighlights();
6680             gotPremove = 0;
6681             ClearPremoveHighlights();
6682         } else {
6683             /* First upclick in same square; start click-click mode */
6684             SetHighlights(x, y, -1, -1);
6685         }
6686         return;
6687     }
6688
6689     clearFlag = 0;
6690
6691     /* we now have a different from- and (possibly off-board) to-square */
6692     /* Completed move */
6693     toX = x;
6694     toY = y;
6695     saveAnimate = appData.animate;
6696     if (clickType == Press) {
6697         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6698             // must be Edit Position mode with empty-square selected
6699             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6700             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6701             return;
6702         }
6703         /* Finish clickclick move */
6704         if (appData.animate || appData.highlightLastMove) {
6705             SetHighlights(fromX, fromY, toX, toY);
6706         } else {
6707             ClearHighlights();
6708         }
6709     } else {
6710         /* Finish drag move */
6711         if (appData.highlightLastMove) {
6712             SetHighlights(fromX, fromY, toX, toY);
6713         } else {
6714             ClearHighlights();
6715         }
6716         DragPieceEnd(xPix, yPix); dragging = 0;
6717         /* Don't animate move and drag both */
6718         appData.animate = FALSE;
6719     }
6720
6721     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6722     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6723         ChessSquare piece = boards[currentMove][fromY][fromX];
6724         if(gameMode == EditPosition && piece != EmptySquare &&
6725            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6726             int n;
6727
6728             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6729                 n = PieceToNumber(piece - (int)BlackPawn);
6730                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6731                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6732                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6733             } else
6734             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6735                 n = PieceToNumber(piece);
6736                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6737                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6738                 boards[currentMove][n][BOARD_WIDTH-2]++;
6739             }
6740             boards[currentMove][fromY][fromX] = EmptySquare;
6741         }
6742         ClearHighlights();
6743         fromX = fromY = -1;
6744         DrawPosition(TRUE, boards[currentMove]);
6745         return;
6746     }
6747
6748     // off-board moves should not be highlighted
6749     if(x < 0 || y < 0) ClearHighlights();
6750
6751     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6752
6753     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6754         SetHighlights(fromX, fromY, toX, toY);
6755         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6756             // [HGM] super: promotion to captured piece selected from holdings
6757             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6758             promotionChoice = TRUE;
6759             // kludge follows to temporarily execute move on display, without promoting yet
6760             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6761             boards[currentMove][toY][toX] = p;
6762             DrawPosition(FALSE, boards[currentMove]);
6763             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6764             boards[currentMove][toY][toX] = q;
6765             DisplayMessage("Click in holdings to choose piece", "");
6766             return;
6767         }
6768         PromotionPopUp();
6769     } else {
6770         int oldMove = currentMove;
6771         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6772         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6773         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6774         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6775            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6776             DrawPosition(TRUE, boards[currentMove]);
6777         fromX = fromY = -1;
6778     }
6779     appData.animate = saveAnimate;
6780     if (appData.animate || appData.animateDragging) {
6781         /* Undo animation damage if needed */
6782         DrawPosition(FALSE, NULL);
6783     }
6784 }
6785
6786 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6787 {   // front-end-free part taken out of PieceMenuPopup
6788     int whichMenu; int xSqr, ySqr;
6789
6790     if(seekGraphUp) { // [HGM] seekgraph
6791         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6792         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6793         return -2;
6794     }
6795
6796     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6797          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6798         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6799         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6800         if(action == Press)   {
6801             originalFlip = flipView;
6802             flipView = !flipView; // temporarily flip board to see game from partners perspective
6803             DrawPosition(TRUE, partnerBoard);
6804             DisplayMessage(partnerStatus, "");
6805             partnerUp = TRUE;
6806         } else if(action == Release) {
6807             flipView = originalFlip;
6808             DrawPosition(TRUE, boards[currentMove]);
6809             partnerUp = FALSE;
6810         }
6811         return -2;
6812     }
6813
6814     xSqr = EventToSquare(x, BOARD_WIDTH);
6815     ySqr = EventToSquare(y, BOARD_HEIGHT);
6816     if (action == Release) {
6817         if(pieceSweep != EmptySquare) {
6818             EditPositionMenuEvent(pieceSweep, toX, toY);
6819             pieceSweep = EmptySquare;
6820         } else UnLoadPV(); // [HGM] pv
6821     }
6822     if (action != Press) return -2; // return code to be ignored
6823     switch (gameMode) {
6824       case IcsExamining:
6825         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6826       case EditPosition:
6827         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6828         if (xSqr < 0 || ySqr < 0) return -1;
6829         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6830         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6831         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6832         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6833         NextPiece(0);
6834         return -2;\r
6835       case IcsObserving:
6836         if(!appData.icsEngineAnalyze) return -1;
6837       case IcsPlayingWhite:
6838       case IcsPlayingBlack:
6839         if(!appData.zippyPlay) goto noZip;
6840       case AnalyzeMode:
6841       case AnalyzeFile:
6842       case MachinePlaysWhite:
6843       case MachinePlaysBlack:
6844       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6845         if (!appData.dropMenu) {
6846           LoadPV(x, y);
6847           return 2; // flag front-end to grab mouse events
6848         }
6849         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6850            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6851       case EditGame:
6852       noZip:
6853         if (xSqr < 0 || ySqr < 0) return -1;
6854         if (!appData.dropMenu || appData.testLegality &&
6855             gameInfo.variant != VariantBughouse &&
6856             gameInfo.variant != VariantCrazyhouse) return -1;
6857         whichMenu = 1; // drop menu
6858         break;
6859       default:
6860         return -1;
6861     }
6862
6863     if (((*fromX = xSqr) < 0) ||
6864         ((*fromY = ySqr) < 0)) {
6865         *fromX = *fromY = -1;
6866         return -1;
6867     }
6868     if (flipView)
6869       *fromX = BOARD_WIDTH - 1 - *fromX;
6870     else
6871       *fromY = BOARD_HEIGHT - 1 - *fromY;
6872
6873     return whichMenu;
6874 }
6875
6876 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6877 {
6878 //    char * hint = lastHint;
6879     FrontEndProgramStats stats;
6880
6881     stats.which = cps == &first ? 0 : 1;
6882     stats.depth = cpstats->depth;
6883     stats.nodes = cpstats->nodes;
6884     stats.score = cpstats->score;
6885     stats.time = cpstats->time;
6886     stats.pv = cpstats->movelist;
6887     stats.hint = lastHint;
6888     stats.an_move_index = 0;
6889     stats.an_move_count = 0;
6890
6891     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6892         stats.hint = cpstats->move_name;
6893         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6894         stats.an_move_count = cpstats->nr_moves;
6895     }
6896
6897     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
6898
6899     SetProgramStats( &stats );
6900 }
6901
6902 void
6903 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6904 {       // count all piece types
6905         int p, f, r;
6906         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6907         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6908         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6909                 p = board[r][f];
6910                 pCnt[p]++;
6911                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6912                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6913                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6914                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6915                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6916                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6917         }
6918 }
6919
6920 int
6921 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6922 {
6923         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6924         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6925
6926         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6927         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6928         if(myPawns == 2 && nMine == 3) // KPP
6929             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6930         if(myPawns == 1 && nMine == 2) // KP
6931             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6932         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6933             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6934         if(myPawns) return FALSE;
6935         if(pCnt[WhiteRook+side])
6936             return pCnt[BlackRook-side] ||
6937                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6938                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6939                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6940         if(pCnt[WhiteCannon+side]) {
6941             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6942             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6943         }
6944         if(pCnt[WhiteKnight+side])
6945             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6946         return FALSE;
6947 }
6948
6949 int
6950 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6951 {
6952         VariantClass v = gameInfo.variant;
6953
6954         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6955         if(v == VariantShatranj) return TRUE; // always winnable through baring
6956         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6957         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6958
6959         if(v == VariantXiangqi) {
6960                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6961
6962                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6963                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6964                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6965                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6966                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6967                 if(stale) // we have at least one last-rank P plus perhaps C
6968                     return majors // KPKX
6969                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6970                 else // KCA*E*
6971                     return pCnt[WhiteFerz+side] // KCAK
6972                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6973                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6974                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6975
6976         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6977                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6978
6979                 if(nMine == 1) return FALSE; // bare King
6980                 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
6981                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6982                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6983                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6984                 if(pCnt[WhiteKnight+side])
6985                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6986                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6987                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6988                 if(nBishops)
6989                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6990                 if(pCnt[WhiteAlfil+side])
6991                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6992                 if(pCnt[WhiteWazir+side])
6993                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6994         }
6995
6996         return TRUE;
6997 }
6998
6999 int
7000 Adjudicate(ChessProgramState *cps)
7001 {       // [HGM] some adjudications useful with buggy engines
7002         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7003         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7004         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7005         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7006         int k, count = 0; static int bare = 1;
7007         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7008         Boolean canAdjudicate = !appData.icsActive;
7009
7010         // most tests only when we understand the game, i.e. legality-checking on
7011             if( appData.testLegality )
7012             {   /* [HGM] Some more adjudications for obstinate engines */
7013                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7014                 static int moveCount = 6;
7015                 ChessMove result;
7016                 char *reason = NULL;
7017
7018                 /* Count what is on board. */
7019                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7020
7021                 /* Some material-based adjudications that have to be made before stalemate test */
7022                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7023                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7024                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7025                      if(canAdjudicate && appData.checkMates) {
7026                          if(engineOpponent)
7027                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7028                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7029                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7030                          return 1;
7031                      }
7032                 }
7033
7034                 /* Bare King in Shatranj (loses) or Losers (wins) */
7035                 if( nrW == 1 || nrB == 1) {
7036                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7037                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7038                      if(canAdjudicate && appData.checkMates) {
7039                          if(engineOpponent)
7040                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7041                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7042                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7043                          return 1;
7044                      }
7045                   } else
7046                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7047                   {    /* bare King */
7048                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7049                         if(canAdjudicate && appData.checkMates) {
7050                             /* but only adjudicate if adjudication enabled */
7051                             if(engineOpponent)
7052                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7053                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7054                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7055                             return 1;
7056                         }
7057                   }
7058                 } else bare = 1;
7059
7060
7061             // don't wait for engine to announce game end if we can judge ourselves
7062             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7063               case MT_CHECK:
7064                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7065                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7066                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7067                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7068                             checkCnt++;
7069                         if(checkCnt >= 2) {
7070                             reason = "Xboard adjudication: 3rd check";
7071                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7072                             break;
7073                         }
7074                     }
7075                 }
7076               case MT_NONE:
7077               default:
7078                 break;
7079               case MT_STALEMATE:
7080               case MT_STAINMATE:
7081                 reason = "Xboard adjudication: Stalemate";
7082                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7083                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7084                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7085                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7086                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7087                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7088                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7089                                                                         EP_CHECKMATE : EP_WINS);
7090                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7091                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7092                 }
7093                 break;
7094               case MT_CHECKMATE:
7095                 reason = "Xboard adjudication: Checkmate";
7096                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7097                 break;
7098             }
7099
7100                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7101                     case EP_STALEMATE:
7102                         result = GameIsDrawn; break;
7103                     case EP_CHECKMATE:
7104                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7105                     case EP_WINS:
7106                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7107                     default:
7108                         result = EndOfFile;
7109                 }
7110                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7111                     if(engineOpponent)
7112                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7113                     GameEnds( result, reason, GE_XBOARD );
7114                     return 1;
7115                 }
7116
7117                 /* Next absolutely insufficient mating material. */
7118                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7119                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7120                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7121
7122                      /* always flag draws, for judging claims */
7123                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7124
7125                      if(canAdjudicate && appData.materialDraws) {
7126                          /* but only adjudicate them if adjudication enabled */
7127                          if(engineOpponent) {
7128                            SendToProgram("force\n", engineOpponent); // suppress reply
7129                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7130                          }
7131                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7132                          return 1;
7133                      }
7134                 }
7135
7136                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7137                 if(gameInfo.variant == VariantXiangqi ?
7138                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7139                  : nrW + nrB == 4 &&
7140                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7141                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7142                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7143                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7144                    ) ) {
7145                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7146                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7147                           if(engineOpponent) {
7148                             SendToProgram("force\n", engineOpponent); // suppress reply
7149                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7150                           }
7151                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7152                           return 1;
7153                      }
7154                 } else moveCount = 6;
7155             }
7156         if (appData.debugMode) { int i;
7157             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7158                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7159                     appData.drawRepeats);
7160             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7161               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7162
7163         }
7164
7165         // Repetition draws and 50-move rule can be applied independently of legality testing
7166
7167                 /* Check for rep-draws */
7168                 count = 0;
7169                 for(k = forwardMostMove-2;
7170                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7171                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7172                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7173                     k-=2)
7174                 {   int rights=0;
7175                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7176                         /* compare castling rights */
7177                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7178                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7179                                 rights++; /* King lost rights, while rook still had them */
7180                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7181                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7182                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7183                                    rights++; /* but at least one rook lost them */
7184                         }
7185                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7186                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7187                                 rights++;
7188                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7189                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7190                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7191                                    rights++;
7192                         }
7193                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7194                             && appData.drawRepeats > 1) {
7195                              /* adjudicate after user-specified nr of repeats */
7196                              int result = GameIsDrawn;
7197                              char *details = "XBoard adjudication: repetition draw";
7198                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7199                                 // [HGM] xiangqi: check for forbidden perpetuals
7200                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7201                                 for(m=forwardMostMove; m>k; m-=2) {
7202                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7203                                         ourPerpetual = 0; // the current mover did not always check
7204                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7205                                         hisPerpetual = 0; // the opponent did not always check
7206                                 }
7207                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7208                                                                         ourPerpetual, hisPerpetual);
7209                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7210                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7211                                     details = "Xboard adjudication: perpetual checking";
7212                                 } else
7213                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7214                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7215                                 } else
7216                                 // Now check for perpetual chases
7217                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7218                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7219                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7220                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7221                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7222                                         details = "Xboard adjudication: perpetual chasing";
7223                                     } else
7224                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7225                                         break; // Abort repetition-checking loop.
7226                                 }
7227                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7228                              }
7229                              if(engineOpponent) {
7230                                SendToProgram("force\n", engineOpponent); // suppress reply
7231                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7232                              }
7233                              GameEnds( result, details, GE_XBOARD );
7234                              return 1;
7235                         }
7236                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7237                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7238                     }
7239                 }
7240
7241                 /* Now we test for 50-move draws. Determine ply count */
7242                 count = forwardMostMove;
7243                 /* look for last irreversble move */
7244                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7245                     count--;
7246                 /* if we hit starting position, add initial plies */
7247                 if( count == backwardMostMove )
7248                     count -= initialRulePlies;
7249                 count = forwardMostMove - count;
7250                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7251                         // adjust reversible move counter for checks in Xiangqi
7252                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7253                         if(i < backwardMostMove) i = backwardMostMove;
7254                         while(i <= forwardMostMove) {
7255                                 lastCheck = inCheck; // check evasion does not count
7256                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7257                                 if(inCheck || lastCheck) count--; // check does not count
7258                                 i++;
7259                         }
7260                 }
7261                 if( count >= 100)
7262                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7263                          /* this is used to judge if draw claims are legal */
7264                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7265                          if(engineOpponent) {
7266                            SendToProgram("force\n", engineOpponent); // suppress reply
7267                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7268                          }
7269                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7270                          return 1;
7271                 }
7272
7273                 /* if draw offer is pending, treat it as a draw claim
7274                  * when draw condition present, to allow engines a way to
7275                  * claim draws before making their move to avoid a race
7276                  * condition occurring after their move
7277                  */
7278                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7279                          char *p = NULL;
7280                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7281                              p = "Draw claim: 50-move rule";
7282                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7283                              p = "Draw claim: 3-fold repetition";
7284                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7285                              p = "Draw claim: insufficient mating material";
7286                          if( p != NULL && canAdjudicate) {
7287                              if(engineOpponent) {
7288                                SendToProgram("force\n", engineOpponent); // suppress reply
7289                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7290                              }
7291                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7292                              return 1;
7293                          }
7294                 }
7295
7296                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7297                     if(engineOpponent) {
7298                       SendToProgram("force\n", engineOpponent); // suppress reply
7299                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7300                     }
7301                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7302                     return 1;
7303                 }
7304         return 0;
7305 }
7306
7307 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7308 {   // [HGM] book: this routine intercepts moves to simulate book replies
7309     char *bookHit = NULL;
7310
7311     //first determine if the incoming move brings opponent into his book
7312     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7313         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7314     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7315     if(bookHit != NULL && !cps->bookSuspend) {
7316         // make sure opponent is not going to reply after receiving move to book position
7317         SendToProgram("force\n", cps);
7318         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7319     }
7320     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7321     // now arrange restart after book miss
7322     if(bookHit) {
7323         // after a book hit we never send 'go', and the code after the call to this routine
7324         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7325         char buf[MSG_SIZ];
7326         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7327         SendToProgram(buf, cps);
7328         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7329     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7330         SendToProgram("go\n", cps);
7331         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7332     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7333         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7334             SendToProgram("go\n", cps);
7335         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7336     }
7337     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7338 }
7339
7340 char *savedMessage;
7341 ChessProgramState *savedState;
7342 void DeferredBookMove(void)
7343 {
7344         if(savedState->lastPing != savedState->lastPong)
7345                     ScheduleDelayedEvent(DeferredBookMove, 10);
7346         else
7347         HandleMachineMove(savedMessage, savedState);
7348 }
7349
7350 void
7351 HandleMachineMove(message, cps)
7352      char *message;
7353      ChessProgramState *cps;
7354 {
7355     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7356     char realname[MSG_SIZ];
7357     int fromX, fromY, toX, toY;
7358     ChessMove moveType;
7359     char promoChar;
7360     char *p;
7361     int machineWhite;
7362     char *bookHit;
7363
7364     cps->userError = 0;
7365
7366 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7367     /*
7368      * Kludge to ignore BEL characters
7369      */
7370     while (*message == '\007') message++;
7371
7372     /*
7373      * [HGM] engine debug message: ignore lines starting with '#' character
7374      */
7375     if(cps->debug && *message == '#') return;
7376
7377     /*
7378      * Look for book output
7379      */
7380     if (cps == &first && bookRequested) {
7381         if (message[0] == '\t' || message[0] == ' ') {
7382             /* Part of the book output is here; append it */
7383             strcat(bookOutput, message);
7384             strcat(bookOutput, "  \n");
7385             return;
7386         } else if (bookOutput[0] != NULLCHAR) {
7387             /* All of book output has arrived; display it */
7388             char *p = bookOutput;
7389             while (*p != NULLCHAR) {
7390                 if (*p == '\t') *p = ' ';
7391                 p++;
7392             }
7393             DisplayInformation(bookOutput);
7394             bookRequested = FALSE;
7395             /* Fall through to parse the current output */
7396         }
7397     }
7398
7399     /*
7400      * Look for machine move.
7401      */
7402     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7403         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7404     {
7405         /* This method is only useful on engines that support ping */
7406         if (cps->lastPing != cps->lastPong) {
7407           if (gameMode == BeginningOfGame) {
7408             /* Extra move from before last new; ignore */
7409             if (appData.debugMode) {
7410                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7411             }
7412           } else {
7413             if (appData.debugMode) {
7414                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7415                         cps->which, gameMode);
7416             }
7417
7418             SendToProgram("undo\n", cps);
7419           }
7420           return;
7421         }
7422
7423         switch (gameMode) {
7424           case BeginningOfGame:
7425             /* Extra move from before last reset; ignore */
7426             if (appData.debugMode) {
7427                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7428             }
7429             return;
7430
7431           case EndOfGame:
7432           case IcsIdle:
7433           default:
7434             /* Extra move after we tried to stop.  The mode test is
7435                not a reliable way of detecting this problem, but it's
7436                the best we can do on engines that don't support ping.
7437             */
7438             if (appData.debugMode) {
7439                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7440                         cps->which, gameMode);
7441             }
7442             SendToProgram("undo\n", cps);
7443             return;
7444
7445           case MachinePlaysWhite:
7446           case IcsPlayingWhite:
7447             machineWhite = TRUE;
7448             break;
7449
7450           case MachinePlaysBlack:
7451           case IcsPlayingBlack:
7452             machineWhite = FALSE;
7453             break;
7454
7455           case TwoMachinesPlay:
7456             machineWhite = (cps->twoMachinesColor[0] == 'w');
7457             break;
7458         }
7459         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7460             if (appData.debugMode) {
7461                 fprintf(debugFP,
7462                         "Ignoring move out of turn by %s, gameMode %d"
7463                         ", forwardMost %d\n",
7464                         cps->which, gameMode, forwardMostMove);
7465             }
7466             return;
7467         }
7468
7469     if (appData.debugMode) { int f = forwardMostMove;
7470         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7471                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7472                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7473     }
7474         if(cps->alphaRank) AlphaRank(machineMove, 4);
7475         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7476                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7477             /* Machine move could not be parsed; ignore it. */
7478           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7479                     machineMove, _(cps->which));
7480             DisplayError(buf1, 0);
7481             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7482                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7483             if (gameMode == TwoMachinesPlay) {
7484               GameEnds(machineWhite ? BlackWins : WhiteWins,
7485                        buf1, GE_XBOARD);
7486             }
7487             return;
7488         }
7489
7490         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7491         /* So we have to redo legality test with true e.p. status here,  */
7492         /* to make sure an illegal e.p. capture does not slip through,   */
7493         /* to cause a forfeit on a justified illegal-move complaint      */
7494         /* of the opponent.                                              */
7495         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7496            ChessMove moveType;
7497            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7498                              fromY, fromX, toY, toX, promoChar);
7499             if (appData.debugMode) {
7500                 int i;
7501                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7502                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7503                 fprintf(debugFP, "castling rights\n");
7504             }
7505             if(moveType == IllegalMove) {
7506               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7507                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7508                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7509                            buf1, GE_XBOARD);
7510                 return;
7511            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7512            /* [HGM] Kludge to handle engines that send FRC-style castling
7513               when they shouldn't (like TSCP-Gothic) */
7514            switch(moveType) {
7515              case WhiteASideCastleFR:
7516              case BlackASideCastleFR:
7517                toX+=2;
7518                currentMoveString[2]++;
7519                break;
7520              case WhiteHSideCastleFR:
7521              case BlackHSideCastleFR:
7522                toX--;
7523                currentMoveString[2]--;
7524                break;
7525              default: ; // nothing to do, but suppresses warning of pedantic compilers
7526            }
7527         }
7528         hintRequested = FALSE;
7529         lastHint[0] = NULLCHAR;
7530         bookRequested = FALSE;
7531         /* Program may be pondering now */
7532         cps->maybeThinking = TRUE;
7533         if (cps->sendTime == 2) cps->sendTime = 1;
7534         if (cps->offeredDraw) cps->offeredDraw--;
7535
7536         /* [AS] Save move info*/
7537         pvInfoList[ forwardMostMove ].score = programStats.score;
7538         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7539         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7540
7541         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7542
7543         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7544         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7545             int count = 0;
7546
7547             while( count < adjudicateLossPlies ) {
7548                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7549
7550                 if( count & 1 ) {
7551                     score = -score; /* Flip score for winning side */
7552                 }
7553
7554                 if( score > adjudicateLossThreshold ) {
7555                     break;
7556                 }
7557
7558                 count++;
7559             }
7560
7561             if( count >= adjudicateLossPlies ) {
7562                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7563
7564                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7565                     "Xboard adjudication",
7566                     GE_XBOARD );
7567
7568                 return;
7569             }
7570         }
7571
7572         if(Adjudicate(cps)) {
7573             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7574             return; // [HGM] adjudicate: for all automatic game ends
7575         }
7576
7577 #if ZIPPY
7578         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7579             first.initDone) {
7580           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7581                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7582                 SendToICS("draw ");
7583                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7584           }
7585           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7586           ics_user_moved = 1;
7587           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7588                 char buf[3*MSG_SIZ];
7589
7590                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7591                         programStats.score / 100.,
7592                         programStats.depth,
7593                         programStats.time / 100.,
7594                         (unsigned int)programStats.nodes,
7595                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7596                         programStats.movelist);
7597                 SendToICS(buf);
7598 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7599           }
7600         }
7601 #endif
7602
7603         /* [AS] Clear stats for next move */
7604         ClearProgramStats();
7605         thinkOutput[0] = NULLCHAR;
7606         hiddenThinkOutputState = 0;
7607
7608         bookHit = NULL;
7609         if (gameMode == TwoMachinesPlay) {
7610             /* [HGM] relaying draw offers moved to after reception of move */
7611             /* and interpreting offer as claim if it brings draw condition */
7612             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7613                 SendToProgram("draw\n", cps->other);
7614             }
7615             if (cps->other->sendTime) {
7616                 SendTimeRemaining(cps->other,
7617                                   cps->other->twoMachinesColor[0] == 'w');
7618             }
7619             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7620             if (firstMove && !bookHit) {
7621                 firstMove = FALSE;
7622                 if (cps->other->useColors) {
7623                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7624                 }
7625                 SendToProgram("go\n", cps->other);
7626             }
7627             cps->other->maybeThinking = TRUE;
7628         }
7629
7630         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7631
7632         if (!pausing && appData.ringBellAfterMoves) {
7633             RingBell();
7634         }
7635
7636         /*
7637          * Reenable menu items that were disabled while
7638          * machine was thinking
7639          */
7640         if (gameMode != TwoMachinesPlay)
7641             SetUserThinkingEnables();
7642
7643         // [HGM] book: after book hit opponent has received move and is now in force mode
7644         // force the book reply into it, and then fake that it outputted this move by jumping
7645         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7646         if(bookHit) {
7647                 static char bookMove[MSG_SIZ]; // a bit generous?
7648
7649                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7650                 strcat(bookMove, bookHit);
7651                 message = bookMove;
7652                 cps = cps->other;
7653                 programStats.nodes = programStats.depth = programStats.time =
7654                 programStats.score = programStats.got_only_move = 0;
7655                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7656
7657                 if(cps->lastPing != cps->lastPong) {
7658                     savedMessage = message; // args for deferred call
7659                     savedState = cps;
7660                     ScheduleDelayedEvent(DeferredBookMove, 10);
7661                     return;
7662                 }
7663                 goto FakeBookMove;
7664         }
7665
7666         return;
7667     }
7668
7669     /* Set special modes for chess engines.  Later something general
7670      *  could be added here; for now there is just one kludge feature,
7671      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7672      *  when "xboard" is given as an interactive command.
7673      */
7674     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7675         cps->useSigint = FALSE;
7676         cps->useSigterm = FALSE;
7677     }
7678     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7679       ParseFeatures(message+8, cps);
7680       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7681     }
7682
7683     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7684       int dummy, s=6; char buf[MSG_SIZ];
7685       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7686       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7687       ParseFEN(boards[0], &dummy, message+s);
7688       DrawPosition(TRUE, boards[0]);
7689       startedFromSetupPosition = TRUE;
7690       return;
7691     }
7692     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7693      * want this, I was asked to put it in, and obliged.
7694      */
7695     if (!strncmp(message, "setboard ", 9)) {
7696         Board initial_position;
7697
7698         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7699
7700         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7701             DisplayError(_("Bad FEN received from engine"), 0);
7702             return ;
7703         } else {
7704            Reset(TRUE, FALSE);
7705            CopyBoard(boards[0], initial_position);
7706            initialRulePlies = FENrulePlies;
7707            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7708            else gameMode = MachinePlaysBlack;
7709            DrawPosition(FALSE, boards[currentMove]);
7710         }
7711         return;
7712     }
7713
7714     /*
7715      * Look for communication commands
7716      */
7717     if (!strncmp(message, "telluser ", 9)) {
7718         if(message[9] == '\\' && message[10] == '\\')
7719             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7720         DisplayNote(message + 9);
7721         return;
7722     }
7723     if (!strncmp(message, "tellusererror ", 14)) {
7724         cps->userError = 1;
7725         if(message[14] == '\\' && message[15] == '\\')
7726             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7727         DisplayError(message + 14, 0);
7728         return;
7729     }
7730     if (!strncmp(message, "tellopponent ", 13)) {
7731       if (appData.icsActive) {
7732         if (loggedOn) {
7733           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7734           SendToICS(buf1);
7735         }
7736       } else {
7737         DisplayNote(message + 13);
7738       }
7739       return;
7740     }
7741     if (!strncmp(message, "tellothers ", 11)) {
7742       if (appData.icsActive) {
7743         if (loggedOn) {
7744           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7745           SendToICS(buf1);
7746         }
7747       }
7748       return;
7749     }
7750     if (!strncmp(message, "tellall ", 8)) {
7751       if (appData.icsActive) {
7752         if (loggedOn) {
7753           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7754           SendToICS(buf1);
7755         }
7756       } else {
7757         DisplayNote(message + 8);
7758       }
7759       return;
7760     }
7761     if (strncmp(message, "warning", 7) == 0) {
7762         /* Undocumented feature, use tellusererror in new code */
7763         DisplayError(message, 0);
7764         return;
7765     }
7766     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7767         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7768         strcat(realname, " query");
7769         AskQuestion(realname, buf2, buf1, cps->pr);
7770         return;
7771     }
7772     /* Commands from the engine directly to ICS.  We don't allow these to be
7773      *  sent until we are logged on. Crafty kibitzes have been known to
7774      *  interfere with the login process.
7775      */
7776     if (loggedOn) {
7777         if (!strncmp(message, "tellics ", 8)) {
7778             SendToICS(message + 8);
7779             SendToICS("\n");
7780             return;
7781         }
7782         if (!strncmp(message, "tellicsnoalias ", 15)) {
7783             SendToICS(ics_prefix);
7784             SendToICS(message + 15);
7785             SendToICS("\n");
7786             return;
7787         }
7788         /* The following are for backward compatibility only */
7789         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7790             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7791             SendToICS(ics_prefix);
7792             SendToICS(message);
7793             SendToICS("\n");
7794             return;
7795         }
7796     }
7797     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7798         return;
7799     }
7800     /*
7801      * If the move is illegal, cancel it and redraw the board.
7802      * Also deal with other error cases.  Matching is rather loose
7803      * here to accommodate engines written before the spec.
7804      */
7805     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7806         strncmp(message, "Error", 5) == 0) {
7807         if (StrStr(message, "name") ||
7808             StrStr(message, "rating") || StrStr(message, "?") ||
7809             StrStr(message, "result") || StrStr(message, "board") ||
7810             StrStr(message, "bk") || StrStr(message, "computer") ||
7811             StrStr(message, "variant") || StrStr(message, "hint") ||
7812             StrStr(message, "random") || StrStr(message, "depth") ||
7813             StrStr(message, "accepted")) {
7814             return;
7815         }
7816         if (StrStr(message, "protover")) {
7817           /* Program is responding to input, so it's apparently done
7818              initializing, and this error message indicates it is
7819              protocol version 1.  So we don't need to wait any longer
7820              for it to initialize and send feature commands. */
7821           FeatureDone(cps, 1);
7822           cps->protocolVersion = 1;
7823           return;
7824         }
7825         cps->maybeThinking = FALSE;
7826
7827         if (StrStr(message, "draw")) {
7828             /* Program doesn't have "draw" command */
7829             cps->sendDrawOffers = 0;
7830             return;
7831         }
7832         if (cps->sendTime != 1 &&
7833             (StrStr(message, "time") || StrStr(message, "otim"))) {
7834           /* Program apparently doesn't have "time" or "otim" command */
7835           cps->sendTime = 0;
7836           return;
7837         }
7838         if (StrStr(message, "analyze")) {
7839             cps->analysisSupport = FALSE;
7840             cps->analyzing = FALSE;
7841             Reset(FALSE, TRUE);
7842             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7843             DisplayError(buf2, 0);
7844             return;
7845         }
7846         if (StrStr(message, "(no matching move)st")) {
7847           /* Special kludge for GNU Chess 4 only */
7848           cps->stKludge = TRUE;
7849           SendTimeControl(cps, movesPerSession, timeControl,
7850                           timeIncrement, appData.searchDepth,
7851                           searchTime);
7852           return;
7853         }
7854         if (StrStr(message, "(no matching move)sd")) {
7855           /* Special kludge for GNU Chess 4 only */
7856           cps->sdKludge = TRUE;
7857           SendTimeControl(cps, movesPerSession, timeControl,
7858                           timeIncrement, appData.searchDepth,
7859                           searchTime);
7860           return;
7861         }
7862         if (!StrStr(message, "llegal")) {
7863             return;
7864         }
7865         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7866             gameMode == IcsIdle) return;
7867         if (forwardMostMove <= backwardMostMove) return;
7868         if (pausing) PauseEvent();
7869       if(appData.forceIllegal) {
7870             // [HGM] illegal: machine refused move; force position after move into it
7871           SendToProgram("force\n", cps);
7872           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7873                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7874                 // when black is to move, while there might be nothing on a2 or black
7875                 // might already have the move. So send the board as if white has the move.
7876                 // But first we must change the stm of the engine, as it refused the last move
7877                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7878                 if(WhiteOnMove(forwardMostMove)) {
7879                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7880                     SendBoard(cps, forwardMostMove); // kludgeless board
7881                 } else {
7882                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7883                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7884                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7885                 }
7886           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7887             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7888                  gameMode == TwoMachinesPlay)
7889               SendToProgram("go\n", cps);
7890             return;
7891       } else
7892         if (gameMode == PlayFromGameFile) {
7893             /* Stop reading this game file */
7894             gameMode = EditGame;
7895             ModeHighlight();
7896         }
7897         /* [HGM] illegal-move claim should forfeit game when Xboard */
7898         /* only passes fully legal moves                            */
7899         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7900             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7901                                 "False illegal-move claim", GE_XBOARD );
7902             return; // do not take back move we tested as valid
7903         }
7904         currentMove = forwardMostMove-1;
7905         DisplayMove(currentMove-1); /* before DisplayMoveError */
7906         SwitchClocks(forwardMostMove-1); // [HGM] race
7907         DisplayBothClocks();
7908         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7909                 parseList[currentMove], _(cps->which));
7910         DisplayMoveError(buf1);
7911         DrawPosition(FALSE, boards[currentMove]);
7912         return;
7913     }
7914     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7915         /* Program has a broken "time" command that
7916            outputs a string not ending in newline.
7917            Don't use it. */
7918         cps->sendTime = 0;
7919     }
7920
7921     /*
7922      * If chess program startup fails, exit with an error message.
7923      * Attempts to recover here are futile.
7924      */
7925     if ((StrStr(message, "unknown host") != NULL)
7926         || (StrStr(message, "No remote directory") != NULL)
7927         || (StrStr(message, "not found") != NULL)
7928         || (StrStr(message, "No such file") != NULL)
7929         || (StrStr(message, "can't alloc") != NULL)
7930         || (StrStr(message, "Permission denied") != NULL)) {
7931
7932         cps->maybeThinking = FALSE;
7933         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7934                 _(cps->which), cps->program, cps->host, message);
7935         RemoveInputSource(cps->isr);
7936         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7937             if(cps == &first) appData.noChessProgram = TRUE;
7938             DisplayError(buf1, 0);
7939         }
7940         return;
7941     }
7942
7943     /*
7944      * Look for hint output
7945      */
7946     if (sscanf(message, "Hint: %s", buf1) == 1) {
7947         if (cps == &first && hintRequested) {
7948             hintRequested = FALSE;
7949             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7950                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7951                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7952                                     PosFlags(forwardMostMove),
7953                                     fromY, fromX, toY, toX, promoChar, buf1);
7954                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7955                 DisplayInformation(buf2);
7956             } else {
7957                 /* Hint move could not be parsed!? */
7958               snprintf(buf2, sizeof(buf2),
7959                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7960                         buf1, _(cps->which));
7961                 DisplayError(buf2, 0);
7962             }
7963         } else {
7964           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7965         }
7966         return;
7967     }
7968
7969     /*
7970      * Ignore other messages if game is not in progress
7971      */
7972     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7973         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7974
7975     /*
7976      * look for win, lose, draw, or draw offer
7977      */
7978     if (strncmp(message, "1-0", 3) == 0) {
7979         char *p, *q, *r = "";
7980         p = strchr(message, '{');
7981         if (p) {
7982             q = strchr(p, '}');
7983             if (q) {
7984                 *q = NULLCHAR;
7985                 r = p + 1;
7986             }
7987         }
7988         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7989         return;
7990     } else if (strncmp(message, "0-1", 3) == 0) {
7991         char *p, *q, *r = "";
7992         p = strchr(message, '{');
7993         if (p) {
7994             q = strchr(p, '}');
7995             if (q) {
7996                 *q = NULLCHAR;
7997                 r = p + 1;
7998             }
7999         }
8000         /* Kludge for Arasan 4.1 bug */
8001         if (strcmp(r, "Black resigns") == 0) {
8002             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8003             return;
8004         }
8005         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8006         return;
8007     } else if (strncmp(message, "1/2", 3) == 0) {
8008         char *p, *q, *r = "";
8009         p = strchr(message, '{');
8010         if (p) {
8011             q = strchr(p, '}');
8012             if (q) {
8013                 *q = NULLCHAR;
8014                 r = p + 1;
8015             }
8016         }
8017
8018         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8019         return;
8020
8021     } else if (strncmp(message, "White resign", 12) == 0) {
8022         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8023         return;
8024     } else if (strncmp(message, "Black resign", 12) == 0) {
8025         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8026         return;
8027     } else if (strncmp(message, "White matches", 13) == 0 ||
8028                strncmp(message, "Black matches", 13) == 0   ) {
8029         /* [HGM] ignore GNUShogi noises */
8030         return;
8031     } else if (strncmp(message, "White", 5) == 0 &&
8032                message[5] != '(' &&
8033                StrStr(message, "Black") == NULL) {
8034         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8035         return;
8036     } else if (strncmp(message, "Black", 5) == 0 &&
8037                message[5] != '(') {
8038         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8039         return;
8040     } else if (strcmp(message, "resign") == 0 ||
8041                strcmp(message, "computer resigns") == 0) {
8042         switch (gameMode) {
8043           case MachinePlaysBlack:
8044           case IcsPlayingBlack:
8045             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8046             break;
8047           case MachinePlaysWhite:
8048           case IcsPlayingWhite:
8049             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8050             break;
8051           case TwoMachinesPlay:
8052             if (cps->twoMachinesColor[0] == 'w')
8053               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8054             else
8055               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8056             break;
8057           default:
8058             /* can't happen */
8059             break;
8060         }
8061         return;
8062     } else if (strncmp(message, "opponent mates", 14) == 0) {
8063         switch (gameMode) {
8064           case MachinePlaysBlack:
8065           case IcsPlayingBlack:
8066             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8067             break;
8068           case MachinePlaysWhite:
8069           case IcsPlayingWhite:
8070             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8071             break;
8072           case TwoMachinesPlay:
8073             if (cps->twoMachinesColor[0] == 'w')
8074               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8075             else
8076               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8077             break;
8078           default:
8079             /* can't happen */
8080             break;
8081         }
8082         return;
8083     } else if (strncmp(message, "computer mates", 14) == 0) {
8084         switch (gameMode) {
8085           case MachinePlaysBlack:
8086           case IcsPlayingBlack:
8087             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8088             break;
8089           case MachinePlaysWhite:
8090           case IcsPlayingWhite:
8091             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8092             break;
8093           case TwoMachinesPlay:
8094             if (cps->twoMachinesColor[0] == 'w')
8095               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8096             else
8097               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8098             break;
8099           default:
8100             /* can't happen */
8101             break;
8102         }
8103         return;
8104     } else if (strncmp(message, "checkmate", 9) == 0) {
8105         if (WhiteOnMove(forwardMostMove)) {
8106             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8107         } else {
8108             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8109         }
8110         return;
8111     } else if (strstr(message, "Draw") != NULL ||
8112                strstr(message, "game is a draw") != NULL) {
8113         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8114         return;
8115     } else if (strstr(message, "offer") != NULL &&
8116                strstr(message, "draw") != NULL) {
8117 #if ZIPPY
8118         if (appData.zippyPlay && first.initDone) {
8119             /* Relay offer to ICS */
8120             SendToICS(ics_prefix);
8121             SendToICS("draw\n");
8122         }
8123 #endif
8124         cps->offeredDraw = 2; /* valid until this engine moves twice */
8125         if (gameMode == TwoMachinesPlay) {
8126             if (cps->other->offeredDraw) {
8127                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8128             /* [HGM] in two-machine mode we delay relaying draw offer      */
8129             /* until after we also have move, to see if it is really claim */
8130             }
8131         } else if (gameMode == MachinePlaysWhite ||
8132                    gameMode == MachinePlaysBlack) {
8133           if (userOfferedDraw) {
8134             DisplayInformation(_("Machine accepts your draw offer"));
8135             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8136           } else {
8137             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8138           }
8139         }
8140     }
8141
8142
8143     /*
8144      * Look for thinking output
8145      */
8146     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8147           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8148                                 ) {
8149         int plylev, mvleft, mvtot, curscore, time;
8150         char mvname[MOVE_LEN];
8151         u64 nodes; // [DM]
8152         char plyext;
8153         int ignore = FALSE;
8154         int prefixHint = FALSE;
8155         mvname[0] = NULLCHAR;
8156
8157         switch (gameMode) {
8158           case MachinePlaysBlack:
8159           case IcsPlayingBlack:
8160             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8161             break;
8162           case MachinePlaysWhite:
8163           case IcsPlayingWhite:
8164             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8165             break;
8166           case AnalyzeMode:
8167           case AnalyzeFile:
8168             break;
8169           case IcsObserving: /* [DM] icsEngineAnalyze */
8170             if (!appData.icsEngineAnalyze) ignore = TRUE;
8171             break;
8172           case TwoMachinesPlay:
8173             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8174                 ignore = TRUE;
8175             }
8176             break;
8177           default:
8178             ignore = TRUE;
8179             break;
8180         }
8181
8182         if (!ignore) {
8183             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8184             buf1[0] = NULLCHAR;
8185             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8186                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8187
8188                 if (plyext != ' ' && plyext != '\t') {
8189                     time *= 100;
8190                 }
8191
8192                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8193                 if( cps->scoreIsAbsolute &&
8194                     ( gameMode == MachinePlaysBlack ||
8195                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8196                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8197                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8198                      !WhiteOnMove(currentMove)
8199                     ) )
8200                 {
8201                     curscore = -curscore;
8202                 }
8203
8204
8205                 tempStats.depth = plylev;
8206                 tempStats.nodes = nodes;
8207                 tempStats.time = time;
8208                 tempStats.score = curscore;
8209                 tempStats.got_only_move = 0;
8210
8211                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8212                         int ticklen;
8213
8214                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8215                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8216                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8217                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8218                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8219                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8220                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8221                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8222                 }
8223
8224                 /* Buffer overflow protection */
8225                 if (buf1[0] != NULLCHAR) {
8226                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8227                         && appData.debugMode) {
8228                         fprintf(debugFP,
8229                                 "PV is too long; using the first %u bytes.\n",
8230                                 (unsigned) sizeof(tempStats.movelist) - 1);
8231                     }
8232
8233                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8234                 } else {
8235                     sprintf(tempStats.movelist, " no PV\n");
8236                 }
8237
8238                 if (tempStats.seen_stat) {
8239                     tempStats.ok_to_send = 1;
8240                 }
8241
8242                 if (strchr(tempStats.movelist, '(') != NULL) {
8243                     tempStats.line_is_book = 1;
8244                     tempStats.nr_moves = 0;
8245                     tempStats.moves_left = 0;
8246                 } else {
8247                     tempStats.line_is_book = 0;
8248                 }
8249
8250                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8251                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8252
8253                 SendProgramStatsToFrontend( cps, &tempStats );
8254
8255                 /*
8256                     [AS] Protect the thinkOutput buffer from overflow... this
8257                     is only useful if buf1 hasn't overflowed first!
8258                 */
8259                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8260                          plylev,
8261                          (gameMode == TwoMachinesPlay ?
8262                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8263                          ((double) curscore) / 100.0,
8264                          prefixHint ? lastHint : "",
8265                          prefixHint ? " " : "" );
8266
8267                 if( buf1[0] != NULLCHAR ) {
8268                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8269
8270                     if( strlen(buf1) > max_len ) {
8271                         if( appData.debugMode) {
8272                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8273                         }
8274                         buf1[max_len+1] = '\0';
8275                     }
8276
8277                     strcat( thinkOutput, buf1 );
8278                 }
8279
8280                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8281                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8282                     DisplayMove(currentMove - 1);
8283                 }
8284                 return;
8285
8286             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8287                 /* crafty (9.25+) says "(only move) <move>"
8288                  * if there is only 1 legal move
8289                  */
8290                 sscanf(p, "(only move) %s", buf1);
8291                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8292                 sprintf(programStats.movelist, "%s (only move)", buf1);
8293                 programStats.depth = 1;
8294                 programStats.nr_moves = 1;
8295                 programStats.moves_left = 1;
8296                 programStats.nodes = 1;
8297                 programStats.time = 1;
8298                 programStats.got_only_move = 1;
8299
8300                 /* Not really, but we also use this member to
8301                    mean "line isn't going to change" (Crafty
8302                    isn't searching, so stats won't change) */
8303                 programStats.line_is_book = 1;
8304
8305                 SendProgramStatsToFrontend( cps, &programStats );
8306
8307                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8308                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8309                     DisplayMove(currentMove - 1);
8310                 }
8311                 return;
8312             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8313                               &time, &nodes, &plylev, &mvleft,
8314                               &mvtot, mvname) >= 5) {
8315                 /* The stat01: line is from Crafty (9.29+) in response
8316                    to the "." command */
8317                 programStats.seen_stat = 1;
8318                 cps->maybeThinking = TRUE;
8319
8320                 if (programStats.got_only_move || !appData.periodicUpdates)
8321                   return;
8322
8323                 programStats.depth = plylev;
8324                 programStats.time = time;
8325                 programStats.nodes = nodes;
8326                 programStats.moves_left = mvleft;
8327                 programStats.nr_moves = mvtot;
8328                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8329                 programStats.ok_to_send = 1;
8330                 programStats.movelist[0] = '\0';
8331
8332                 SendProgramStatsToFrontend( cps, &programStats );
8333
8334                 return;
8335
8336             } else if (strncmp(message,"++",2) == 0) {
8337                 /* Crafty 9.29+ outputs this */
8338                 programStats.got_fail = 2;
8339                 return;
8340
8341             } else if (strncmp(message,"--",2) == 0) {
8342                 /* Crafty 9.29+ outputs this */
8343                 programStats.got_fail = 1;
8344                 return;
8345
8346             } else if (thinkOutput[0] != NULLCHAR &&
8347                        strncmp(message, "    ", 4) == 0) {
8348                 unsigned message_len;
8349
8350                 p = message;
8351                 while (*p && *p == ' ') p++;
8352
8353                 message_len = strlen( p );
8354
8355                 /* [AS] Avoid buffer overflow */
8356                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8357                     strcat(thinkOutput, " ");
8358                     strcat(thinkOutput, p);
8359                 }
8360
8361                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8362                     strcat(programStats.movelist, " ");
8363                     strcat(programStats.movelist, p);
8364                 }
8365
8366                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8367                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8368                     DisplayMove(currentMove - 1);
8369                 }
8370                 return;
8371             }
8372         }
8373         else {
8374             buf1[0] = NULLCHAR;
8375
8376             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8377                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8378             {
8379                 ChessProgramStats cpstats;
8380
8381                 if (plyext != ' ' && plyext != '\t') {
8382                     time *= 100;
8383                 }
8384
8385                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8386                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8387                     curscore = -curscore;
8388                 }
8389
8390                 cpstats.depth = plylev;
8391                 cpstats.nodes = nodes;
8392                 cpstats.time = time;
8393                 cpstats.score = curscore;
8394                 cpstats.got_only_move = 0;
8395                 cpstats.movelist[0] = '\0';
8396
8397                 if (buf1[0] != NULLCHAR) {
8398                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8399                 }
8400
8401                 cpstats.ok_to_send = 0;
8402                 cpstats.line_is_book = 0;
8403                 cpstats.nr_moves = 0;
8404                 cpstats.moves_left = 0;
8405
8406                 SendProgramStatsToFrontend( cps, &cpstats );
8407             }
8408         }
8409     }
8410 }
8411
8412
8413 /* Parse a game score from the character string "game", and
8414    record it as the history of the current game.  The game
8415    score is NOT assumed to start from the standard position.
8416    The display is not updated in any way.
8417    */
8418 void
8419 ParseGameHistory(game)
8420      char *game;
8421 {
8422     ChessMove moveType;
8423     int fromX, fromY, toX, toY, boardIndex;
8424     char promoChar;
8425     char *p, *q;
8426     char buf[MSG_SIZ];
8427
8428     if (appData.debugMode)
8429       fprintf(debugFP, "Parsing game history: %s\n", game);
8430
8431     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8432     gameInfo.site = StrSave(appData.icsHost);
8433     gameInfo.date = PGNDate();
8434     gameInfo.round = StrSave("-");
8435
8436     /* Parse out names of players */
8437     while (*game == ' ') game++;
8438     p = buf;
8439     while (*game != ' ') *p++ = *game++;
8440     *p = NULLCHAR;
8441     gameInfo.white = StrSave(buf);
8442     while (*game == ' ') game++;
8443     p = buf;
8444     while (*game != ' ' && *game != '\n') *p++ = *game++;
8445     *p = NULLCHAR;
8446     gameInfo.black = StrSave(buf);
8447
8448     /* Parse moves */
8449     boardIndex = blackPlaysFirst ? 1 : 0;
8450     yynewstr(game);
8451     for (;;) {
8452         yyboardindex = boardIndex;
8453         moveType = (ChessMove) Myylex();
8454         switch (moveType) {
8455           case IllegalMove:             /* maybe suicide chess, etc. */
8456   if (appData.debugMode) {
8457     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8458     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8459     setbuf(debugFP, NULL);
8460   }
8461           case WhitePromotion:
8462           case BlackPromotion:
8463           case WhiteNonPromotion:
8464           case BlackNonPromotion:
8465           case NormalMove:
8466           case WhiteCapturesEnPassant:
8467           case BlackCapturesEnPassant:
8468           case WhiteKingSideCastle:
8469           case WhiteQueenSideCastle:
8470           case BlackKingSideCastle:
8471           case BlackQueenSideCastle:
8472           case WhiteKingSideCastleWild:
8473           case WhiteQueenSideCastleWild:
8474           case BlackKingSideCastleWild:
8475           case BlackQueenSideCastleWild:
8476           /* PUSH Fabien */
8477           case WhiteHSideCastleFR:
8478           case WhiteASideCastleFR:
8479           case BlackHSideCastleFR:
8480           case BlackASideCastleFR:
8481           /* POP Fabien */
8482             fromX = currentMoveString[0] - AAA;
8483             fromY = currentMoveString[1] - ONE;
8484             toX = currentMoveString[2] - AAA;
8485             toY = currentMoveString[3] - ONE;
8486             promoChar = currentMoveString[4];
8487             break;
8488           case WhiteDrop:
8489           case BlackDrop:
8490             fromX = moveType == WhiteDrop ?
8491               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8492             (int) CharToPiece(ToLower(currentMoveString[0]));
8493             fromY = DROP_RANK;
8494             toX = currentMoveString[2] - AAA;
8495             toY = currentMoveString[3] - ONE;
8496             promoChar = NULLCHAR;
8497             break;
8498           case AmbiguousMove:
8499             /* bug? */
8500             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8501   if (appData.debugMode) {
8502     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8503     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8504     setbuf(debugFP, NULL);
8505   }
8506             DisplayError(buf, 0);
8507             return;
8508           case ImpossibleMove:
8509             /* bug? */
8510             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8511   if (appData.debugMode) {
8512     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8513     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8514     setbuf(debugFP, NULL);
8515   }
8516             DisplayError(buf, 0);
8517             return;
8518           case EndOfFile:
8519             if (boardIndex < backwardMostMove) {
8520                 /* Oops, gap.  How did that happen? */
8521                 DisplayError(_("Gap in move list"), 0);
8522                 return;
8523             }
8524             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8525             if (boardIndex > forwardMostMove) {
8526                 forwardMostMove = boardIndex;
8527             }
8528             return;
8529           case ElapsedTime:
8530             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8531                 strcat(parseList[boardIndex-1], " ");
8532                 strcat(parseList[boardIndex-1], yy_text);
8533             }
8534             continue;
8535           case Comment:
8536           case PGNTag:
8537           case NAG:
8538           default:
8539             /* ignore */
8540             continue;
8541           case WhiteWins:
8542           case BlackWins:
8543           case GameIsDrawn:
8544           case GameUnfinished:
8545             if (gameMode == IcsExamining) {
8546                 if (boardIndex < backwardMostMove) {
8547                     /* Oops, gap.  How did that happen? */
8548                     return;
8549                 }
8550                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8551                 return;
8552             }
8553             gameInfo.result = moveType;
8554             p = strchr(yy_text, '{');
8555             if (p == NULL) p = strchr(yy_text, '(');
8556             if (p == NULL) {
8557                 p = yy_text;
8558                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8559             } else {
8560                 q = strchr(p, *p == '{' ? '}' : ')');
8561                 if (q != NULL) *q = NULLCHAR;
8562                 p++;
8563             }
8564             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8565             gameInfo.resultDetails = StrSave(p);
8566             continue;
8567         }
8568         if (boardIndex >= forwardMostMove &&
8569             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8570             backwardMostMove = blackPlaysFirst ? 1 : 0;
8571             return;
8572         }
8573         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8574                                  fromY, fromX, toY, toX, promoChar,
8575                                  parseList[boardIndex]);
8576         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8577         /* currentMoveString is set as a side-effect of yylex */
8578         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8579         strcat(moveList[boardIndex], "\n");
8580         boardIndex++;
8581         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8582         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8583           case MT_NONE:
8584           case MT_STALEMATE:
8585           default:
8586             break;
8587           case MT_CHECK:
8588             if(gameInfo.variant != VariantShogi)
8589                 strcat(parseList[boardIndex - 1], "+");
8590             break;
8591           case MT_CHECKMATE:
8592           case MT_STAINMATE:
8593             strcat(parseList[boardIndex - 1], "#");
8594             break;
8595         }
8596     }
8597 }
8598
8599
8600 /* Apply a move to the given board  */
8601 void
8602 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8603      int fromX, fromY, toX, toY;
8604      int promoChar;
8605      Board board;
8606 {
8607   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8608   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8609
8610     /* [HGM] compute & store e.p. status and castling rights for new position */
8611     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8612
8613       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8614       oldEP = (signed char)board[EP_STATUS];
8615       board[EP_STATUS] = EP_NONE;
8616
8617       if( board[toY][toX] != EmptySquare )
8618            board[EP_STATUS] = EP_CAPTURE;
8619
8620   if (fromY == DROP_RANK) {
8621         /* must be first */
8622         piece = board[toY][toX] = (ChessSquare) fromX;
8623   } else {
8624       int i;
8625
8626       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8627            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8628                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8629       } else
8630       if( board[fromY][fromX] == WhitePawn ) {
8631            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8632                board[EP_STATUS] = EP_PAWN_MOVE;
8633            if( toY-fromY==2) {
8634                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8635                         gameInfo.variant != VariantBerolina || toX < fromX)
8636                       board[EP_STATUS] = toX | berolina;
8637                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8638                         gameInfo.variant != VariantBerolina || toX > fromX)
8639                       board[EP_STATUS] = toX;
8640            }
8641       } else
8642       if( board[fromY][fromX] == BlackPawn ) {
8643            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8644                board[EP_STATUS] = EP_PAWN_MOVE;
8645            if( toY-fromY== -2) {
8646                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8647                         gameInfo.variant != VariantBerolina || toX < fromX)
8648                       board[EP_STATUS] = toX | berolina;
8649                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8650                         gameInfo.variant != VariantBerolina || toX > fromX)
8651                       board[EP_STATUS] = toX;
8652            }
8653        }
8654
8655        for(i=0; i<nrCastlingRights; i++) {
8656            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8657               board[CASTLING][i] == toX   && castlingRank[i] == toY
8658              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8659        }
8660
8661      if (fromX == toX && fromY == toY) return;
8662
8663      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8664      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8665      if(gameInfo.variant == VariantKnightmate)
8666          king += (int) WhiteUnicorn - (int) WhiteKing;
8667
8668     /* Code added by Tord: */
8669     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8670     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8671         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8672       board[fromY][fromX] = EmptySquare;
8673       board[toY][toX] = EmptySquare;
8674       if((toX > fromX) != (piece == WhiteRook)) {
8675         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8676       } else {
8677         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8678       }
8679     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8680                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8681       board[fromY][fromX] = EmptySquare;
8682       board[toY][toX] = EmptySquare;
8683       if((toX > fromX) != (piece == BlackRook)) {
8684         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8685       } else {
8686         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8687       }
8688     /* End of code added by Tord */
8689
8690     } else if (board[fromY][fromX] == king
8691         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8692         && toY == fromY && toX > fromX+1) {
8693         board[fromY][fromX] = EmptySquare;
8694         board[toY][toX] = king;
8695         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8696         board[fromY][BOARD_RGHT-1] = EmptySquare;
8697     } else if (board[fromY][fromX] == king
8698         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8699                && toY == fromY && toX < fromX-1) {
8700         board[fromY][fromX] = EmptySquare;
8701         board[toY][toX] = king;
8702         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8703         board[fromY][BOARD_LEFT] = EmptySquare;
8704     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8705                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8706                && toY >= BOARD_HEIGHT-promoRank
8707                ) {
8708         /* white pawn promotion */
8709         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8710         if (board[toY][toX] == EmptySquare) {
8711             board[toY][toX] = WhiteQueen;
8712         }
8713         if(gameInfo.variant==VariantBughouse ||
8714            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8715             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8716         board[fromY][fromX] = EmptySquare;
8717     } else if ((fromY == BOARD_HEIGHT-4)
8718                && (toX != fromX)
8719                && gameInfo.variant != VariantXiangqi
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         captured = board[toY - 1][toX];
8726         board[toY - 1][toX] = EmptySquare;
8727     } else if ((fromY == BOARD_HEIGHT-4)
8728                && (toX == fromX)
8729                && gameInfo.variant == VariantBerolina
8730                && (board[fromY][fromX] == WhitePawn)
8731                && (board[toY][toX] == EmptySquare)) {
8732         board[fromY][fromX] = EmptySquare;
8733         board[toY][toX] = WhitePawn;
8734         if(oldEP & EP_BEROLIN_A) {
8735                 captured = board[fromY][fromX-1];
8736                 board[fromY][fromX-1] = EmptySquare;
8737         }else{  captured = board[fromY][fromX+1];
8738                 board[fromY][fromX+1] = EmptySquare;
8739         }
8740     } else if (board[fromY][fromX] == king
8741         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8742                && toY == fromY && toX > fromX+1) {
8743         board[fromY][fromX] = EmptySquare;
8744         board[toY][toX] = king;
8745         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8746         board[fromY][BOARD_RGHT-1] = EmptySquare;
8747     } else if (board[fromY][fromX] == king
8748         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8749                && toY == fromY && toX < fromX-1) {
8750         board[fromY][fromX] = EmptySquare;
8751         board[toY][toX] = king;
8752         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8753         board[fromY][BOARD_LEFT] = EmptySquare;
8754     } else if (fromY == 7 && fromX == 3
8755                && board[fromY][fromX] == BlackKing
8756                && toY == 7 && toX == 5) {
8757         board[fromY][fromX] = EmptySquare;
8758         board[toY][toX] = BlackKing;
8759         board[fromY][7] = EmptySquare;
8760         board[toY][4] = BlackRook;
8761     } else if (fromY == 7 && fromX == 3
8762                && board[fromY][fromX] == BlackKing
8763                && toY == 7 && toX == 1) {
8764         board[fromY][fromX] = EmptySquare;
8765         board[toY][toX] = BlackKing;
8766         board[fromY][0] = EmptySquare;
8767         board[toY][2] = BlackRook;
8768     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8769                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8770                && toY < promoRank
8771                ) {
8772         /* black pawn promotion */
8773         board[toY][toX] = CharToPiece(ToLower(promoChar));
8774         if (board[toY][toX] == EmptySquare) {
8775             board[toY][toX] = BlackQueen;
8776         }
8777         if(gameInfo.variant==VariantBughouse ||
8778            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8779             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8780         board[fromY][fromX] = EmptySquare;
8781     } else if ((fromY == 3)
8782                && (toX != fromX)
8783                && gameInfo.variant != VariantXiangqi
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         captured = board[toY + 1][toX];
8790         board[toY + 1][toX] = EmptySquare;
8791     } else if ((fromY == 3)
8792                && (toX == fromX)
8793                && gameInfo.variant == VariantBerolina
8794                && (board[fromY][fromX] == BlackPawn)
8795                && (board[toY][toX] == EmptySquare)) {
8796         board[fromY][fromX] = EmptySquare;
8797         board[toY][toX] = BlackPawn;
8798         if(oldEP & EP_BEROLIN_A) {
8799                 captured = board[fromY][fromX-1];
8800                 board[fromY][fromX-1] = EmptySquare;
8801         }else{  captured = board[fromY][fromX+1];
8802                 board[fromY][fromX+1] = EmptySquare;
8803         }
8804     } else {
8805         board[toY][toX] = board[fromY][fromX];
8806         board[fromY][fromX] = EmptySquare;
8807     }
8808   }
8809
8810     if (gameInfo.holdingsWidth != 0) {
8811
8812       /* !!A lot more code needs to be written to support holdings  */
8813       /* [HGM] OK, so I have written it. Holdings are stored in the */
8814       /* penultimate board files, so they are automaticlly stored   */
8815       /* in the game history.                                       */
8816       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8817                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8818         /* Delete from holdings, by decreasing count */
8819         /* and erasing image if necessary            */
8820         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8821         if(p < (int) BlackPawn) { /* white drop */
8822              p -= (int)WhitePawn;
8823                  p = PieceToNumber((ChessSquare)p);
8824              if(p >= gameInfo.holdingsSize) p = 0;
8825              if(--board[p][BOARD_WIDTH-2] <= 0)
8826                   board[p][BOARD_WIDTH-1] = EmptySquare;
8827              if((int)board[p][BOARD_WIDTH-2] < 0)
8828                         board[p][BOARD_WIDTH-2] = 0;
8829         } else {                  /* black drop */
8830              p -= (int)BlackPawn;
8831                  p = PieceToNumber((ChessSquare)p);
8832              if(p >= gameInfo.holdingsSize) p = 0;
8833              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8834                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8835              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8836                         board[BOARD_HEIGHT-1-p][1] = 0;
8837         }
8838       }
8839       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8840           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8841         /* [HGM] holdings: Add to holdings, if holdings exist */
8842         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8843                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8844                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8845         }
8846         p = (int) captured;
8847         if (p >= (int) BlackPawn) {
8848           p -= (int)BlackPawn;
8849           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8850                   /* in Shogi restore piece to its original  first */
8851                   captured = (ChessSquare) (DEMOTED captured);
8852                   p = DEMOTED p;
8853           }
8854           p = PieceToNumber((ChessSquare)p);
8855           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8856           board[p][BOARD_WIDTH-2]++;
8857           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8858         } else {
8859           p -= (int)WhitePawn;
8860           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8861                   captured = (ChessSquare) (DEMOTED captured);
8862                   p = DEMOTED p;
8863           }
8864           p = PieceToNumber((ChessSquare)p);
8865           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8866           board[BOARD_HEIGHT-1-p][1]++;
8867           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8868         }
8869       }
8870     } else if (gameInfo.variant == VariantAtomic) {
8871       if (captured != EmptySquare) {
8872         int y, x;
8873         for (y = toY-1; y <= toY+1; y++) {
8874           for (x = toX-1; x <= toX+1; x++) {
8875             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8876                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8877               board[y][x] = EmptySquare;
8878             }
8879           }
8880         }
8881         board[toY][toX] = EmptySquare;
8882       }
8883     }
8884     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8885         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8886     } else
8887     if(promoChar == '+') {
8888         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8889         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8890     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8891         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8892     }
8893     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8894                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8895         // [HGM] superchess: take promotion piece out of holdings
8896         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8897         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8898             if(!--board[k][BOARD_WIDTH-2])
8899                 board[k][BOARD_WIDTH-1] = EmptySquare;
8900         } else {
8901             if(!--board[BOARD_HEIGHT-1-k][1])
8902                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8903         }
8904     }
8905
8906 }
8907
8908 /* Updates forwardMostMove */
8909 void
8910 MakeMove(fromX, fromY, toX, toY, promoChar)
8911      int fromX, fromY, toX, toY;
8912      int promoChar;
8913 {
8914 //    forwardMostMove++; // [HGM] bare: moved downstream
8915
8916     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8917         int timeLeft; static int lastLoadFlag=0; int king, piece;
8918         piece = boards[forwardMostMove][fromY][fromX];
8919         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8920         if(gameInfo.variant == VariantKnightmate)
8921             king += (int) WhiteUnicorn - (int) WhiteKing;
8922         if(forwardMostMove == 0) {
8923             if(blackPlaysFirst)
8924                 fprintf(serverMoves, "%s;", second.tidy);
8925             fprintf(serverMoves, "%s;", first.tidy);
8926             if(!blackPlaysFirst)
8927                 fprintf(serverMoves, "%s;", second.tidy);
8928         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8929         lastLoadFlag = loadFlag;
8930         // print base move
8931         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8932         // print castling suffix
8933         if( toY == fromY && piece == king ) {
8934             if(toX-fromX > 1)
8935                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8936             if(fromX-toX >1)
8937                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8938         }
8939         // e.p. suffix
8940         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8941              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8942              boards[forwardMostMove][toY][toX] == EmptySquare
8943              && fromX != toX && fromY != toY)
8944                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8945         // promotion suffix
8946         if(promoChar != NULLCHAR)
8947                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8948         if(!loadFlag) {
8949             fprintf(serverMoves, "/%d/%d",
8950                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8951             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8952             else                      timeLeft = blackTimeRemaining/1000;
8953             fprintf(serverMoves, "/%d", timeLeft);
8954         }
8955         fflush(serverMoves);
8956     }
8957
8958     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8959       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8960                         0, 1);
8961       return;
8962     }
8963     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8964     if (commentList[forwardMostMove+1] != NULL) {
8965         free(commentList[forwardMostMove+1]);
8966         commentList[forwardMostMove+1] = NULL;
8967     }
8968     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8969     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8970     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8971     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8972     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8973     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8974     gameInfo.result = GameUnfinished;
8975     if (gameInfo.resultDetails != NULL) {
8976         free(gameInfo.resultDetails);
8977         gameInfo.resultDetails = NULL;
8978     }
8979     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8980                               moveList[forwardMostMove - 1]);
8981     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8982                              PosFlags(forwardMostMove - 1),
8983                              fromY, fromX, toY, toX, promoChar,
8984                              parseList[forwardMostMove - 1]);
8985     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8986       case MT_NONE:
8987       case MT_STALEMATE:
8988       default:
8989         break;
8990       case MT_CHECK:
8991         if(gameInfo.variant != VariantShogi)
8992             strcat(parseList[forwardMostMove - 1], "+");
8993         break;
8994       case MT_CHECKMATE:
8995       case MT_STAINMATE:
8996         strcat(parseList[forwardMostMove - 1], "#");
8997         break;
8998     }
8999     if (appData.debugMode) {
9000         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9001     }
9002
9003 }
9004
9005 /* Updates currentMove if not pausing */
9006 void
9007 ShowMove(fromX, fromY, toX, toY)
9008 {
9009     int instant = (gameMode == PlayFromGameFile) ?
9010         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9011     if(appData.noGUI) return;
9012     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9013         if (!instant) {
9014             if (forwardMostMove == currentMove + 1) {
9015                 AnimateMove(boards[forwardMostMove - 1],
9016                             fromX, fromY, toX, toY);
9017             }
9018             if (appData.highlightLastMove) {
9019                 SetHighlights(fromX, fromY, toX, toY);
9020             }
9021         }
9022         currentMove = forwardMostMove;
9023     }
9024
9025     if (instant) return;
9026
9027     DisplayMove(currentMove - 1);
9028     DrawPosition(FALSE, boards[currentMove]);
9029     DisplayBothClocks();
9030     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9031 }
9032
9033 void SendEgtPath(ChessProgramState *cps)
9034 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9035         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9036
9037         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9038
9039         while(*p) {
9040             char c, *q = name+1, *r, *s;
9041
9042             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9043             while(*p && *p != ',') *q++ = *p++;
9044             *q++ = ':'; *q = 0;
9045             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9046                 strcmp(name, ",nalimov:") == 0 ) {
9047                 // take nalimov path from the menu-changeable option first, if it is defined
9048               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9049                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9050             } else
9051             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9052                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9053                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9054                 s = r = StrStr(s, ":") + 1; // beginning of path info
9055                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9056                 c = *r; *r = 0;             // temporarily null-terminate path info
9057                     *--q = 0;               // strip of trailig ':' from name
9058                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9059                 *r = c;
9060                 SendToProgram(buf,cps);     // send egtbpath command for this format
9061             }
9062             if(*p == ',') p++; // read away comma to position for next format name
9063         }
9064 }
9065
9066 void
9067 InitChessProgram(cps, setup)
9068      ChessProgramState *cps;
9069      int setup; /* [HGM] needed to setup FRC opening position */
9070 {
9071     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9072     if (appData.noChessProgram) return;
9073     hintRequested = FALSE;
9074     bookRequested = FALSE;
9075
9076     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9077     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9078     if(cps->memSize) { /* [HGM] memory */
9079       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9080         SendToProgram(buf, cps);
9081     }
9082     SendEgtPath(cps); /* [HGM] EGT */
9083     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9084       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9085         SendToProgram(buf, cps);
9086     }
9087
9088     SendToProgram(cps->initString, cps);
9089     if (gameInfo.variant != VariantNormal &&
9090         gameInfo.variant != VariantLoadable
9091         /* [HGM] also send variant if board size non-standard */
9092         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9093                                             ) {
9094       char *v = VariantName(gameInfo.variant);
9095       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9096         /* [HGM] in protocol 1 we have to assume all variants valid */
9097         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9098         DisplayFatalError(buf, 0, 1);
9099         return;
9100       }
9101
9102       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9103       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9104       if( gameInfo.variant == VariantXiangqi )
9105            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9106       if( gameInfo.variant == VariantShogi )
9107            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9108       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9109            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9110       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9111           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9112            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9113       if( gameInfo.variant == VariantCourier )
9114            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9115       if( gameInfo.variant == VariantSuper )
9116            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9117       if( gameInfo.variant == VariantGreat )
9118            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9119       if( gameInfo.variant == VariantSChess )
9120            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9121
9122       if(overruled) {
9123         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9124                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9125            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9126            if(StrStr(cps->variants, b) == NULL) {
9127                // specific sized variant not known, check if general sizing allowed
9128                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9129                    if(StrStr(cps->variants, "boardsize") == NULL) {
9130                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9131                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9132                        DisplayFatalError(buf, 0, 1);
9133                        return;
9134                    }
9135                    /* [HGM] here we really should compare with the maximum supported board size */
9136                }
9137            }
9138       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9139       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9140       SendToProgram(buf, cps);
9141     }
9142     currentlyInitializedVariant = gameInfo.variant;
9143
9144     /* [HGM] send opening position in FRC to first engine */
9145     if(setup) {
9146           SendToProgram("force\n", cps);
9147           SendBoard(cps, 0);
9148           /* engine is now in force mode! Set flag to wake it up after first move. */
9149           setboardSpoiledMachineBlack = 1;
9150     }
9151
9152     if (cps->sendICS) {
9153       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9154       SendToProgram(buf, cps);
9155     }
9156     cps->maybeThinking = FALSE;
9157     cps->offeredDraw = 0;
9158     if (!appData.icsActive) {
9159         SendTimeControl(cps, movesPerSession, timeControl,
9160                         timeIncrement, appData.searchDepth,
9161                         searchTime);
9162     }
9163     if (appData.showThinking
9164         // [HGM] thinking: four options require thinking output to be sent
9165         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9166                                 ) {
9167         SendToProgram("post\n", cps);
9168     }
9169     SendToProgram("hard\n", cps);
9170     if (!appData.ponderNextMove) {
9171         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9172            it without being sure what state we are in first.  "hard"
9173            is not a toggle, so that one is OK.
9174          */
9175         SendToProgram("easy\n", cps);
9176     }
9177     if (cps->usePing) {
9178       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9179       SendToProgram(buf, cps);
9180     }
9181     cps->initDone = TRUE;
9182 }
9183
9184
9185 void
9186 StartChessProgram(cps)
9187      ChessProgramState *cps;
9188 {
9189     char buf[MSG_SIZ];
9190     int err;
9191
9192     if (appData.noChessProgram) return;
9193     cps->initDone = FALSE;
9194
9195     if (strcmp(cps->host, "localhost") == 0) {
9196         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9197     } else if (*appData.remoteShell == NULLCHAR) {
9198         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9199     } else {
9200         if (*appData.remoteUser == NULLCHAR) {
9201           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9202                     cps->program);
9203         } else {
9204           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9205                     cps->host, appData.remoteUser, cps->program);
9206         }
9207         err = StartChildProcess(buf, "", &cps->pr);
9208     }
9209
9210     if (err != 0) {
9211       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9212         DisplayFatalError(buf, err, 1);
9213         cps->pr = NoProc;
9214         cps->isr = NULL;
9215         return;
9216     }
9217
9218     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9219     if (cps->protocolVersion > 1) {
9220       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9221       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9222       cps->comboCnt = 0;  //                and values of combo boxes
9223       SendToProgram(buf, cps);
9224     } else {
9225       SendToProgram("xboard\n", cps);
9226     }
9227 }
9228
9229
9230 void
9231 TwoMachinesEventIfReady P((void))
9232 {
9233   if (first.lastPing != first.lastPong) {
9234     DisplayMessage("", _("Waiting for first chess program"));
9235     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9236     return;
9237   }
9238   if (second.lastPing != second.lastPong) {
9239     DisplayMessage("", _("Waiting for second chess program"));
9240     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9241     return;
9242   }
9243   ThawUI();
9244   TwoMachinesEvent();
9245 }
9246
9247 void
9248 NextMatchGame P((void))
9249 {
9250     int index; /* [HGM] autoinc: step load index during match */
9251     Reset(FALSE, TRUE);
9252     if (*appData.loadGameFile != NULLCHAR) {
9253         index = appData.loadGameIndex;
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         LoadGameFromFile(appData.loadGameFile,
9259                          index,
9260                          appData.loadGameFile, FALSE);
9261     } else if (*appData.loadPositionFile != NULLCHAR) {
9262         index = appData.loadPositionIndex;
9263         if(index < 0) { // [HGM] autoinc
9264             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9265             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9266         }
9267         LoadPositionFromFile(appData.loadPositionFile,
9268                              index,
9269                              appData.loadPositionFile);
9270     }
9271     TwoMachinesEventIfReady();
9272 }
9273
9274 void UserAdjudicationEvent( int result )
9275 {
9276     ChessMove gameResult = GameIsDrawn;
9277
9278     if( result > 0 ) {
9279         gameResult = WhiteWins;
9280     }
9281     else if( result < 0 ) {
9282         gameResult = BlackWins;
9283     }
9284
9285     if( gameMode == TwoMachinesPlay ) {
9286         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9287     }
9288 }
9289
9290
9291 // [HGM] save: calculate checksum of game to make games easily identifiable
9292 int StringCheckSum(char *s)
9293 {
9294         int i = 0;
9295         if(s==NULL) return 0;
9296         while(*s) i = i*259 + *s++;
9297         return i;
9298 }
9299
9300 int GameCheckSum()
9301 {
9302         int i, sum=0;
9303         for(i=backwardMostMove; i<forwardMostMove; i++) {
9304                 sum += pvInfoList[i].depth;
9305                 sum += StringCheckSum(parseList[i]);
9306                 sum += StringCheckSum(commentList[i]);
9307                 sum *= 261;
9308         }
9309         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9310         return sum + StringCheckSum(commentList[i]);
9311 } // end of save patch
9312
9313 void
9314 GameEnds(result, resultDetails, whosays)
9315      ChessMove result;
9316      char *resultDetails;
9317      int whosays;
9318 {
9319     GameMode nextGameMode;
9320     int isIcsGame;
9321     char buf[MSG_SIZ], popupRequested = 0;
9322
9323     if(endingGame) return; /* [HGM] crash: forbid recursion */
9324     endingGame = 1;
9325     if(twoBoards) { // [HGM] dual: switch back to one board
9326         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9327         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9328     }
9329     if (appData.debugMode) {
9330       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9331               result, resultDetails ? resultDetails : "(null)", whosays);
9332     }
9333
9334     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9335
9336     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9337         /* If we are playing on ICS, the server decides when the
9338            game is over, but the engine can offer to draw, claim
9339            a draw, or resign.
9340          */
9341 #if ZIPPY
9342         if (appData.zippyPlay && first.initDone) {
9343             if (result == GameIsDrawn) {
9344                 /* In case draw still needs to be claimed */
9345                 SendToICS(ics_prefix);
9346                 SendToICS("draw\n");
9347             } else if (StrCaseStr(resultDetails, "resign")) {
9348                 SendToICS(ics_prefix);
9349                 SendToICS("resign\n");
9350             }
9351         }
9352 #endif
9353         endingGame = 0; /* [HGM] crash */
9354         return;
9355     }
9356
9357     /* If we're loading the game from a file, stop */
9358     if (whosays == GE_FILE) {
9359       (void) StopLoadGameTimer();
9360       gameFileFP = NULL;
9361     }
9362
9363     /* Cancel draw offers */
9364     first.offeredDraw = second.offeredDraw = 0;
9365
9366     /* If this is an ICS game, only ICS can really say it's done;
9367        if not, anyone can. */
9368     isIcsGame = (gameMode == IcsPlayingWhite ||
9369                  gameMode == IcsPlayingBlack ||
9370                  gameMode == IcsObserving    ||
9371                  gameMode == IcsExamining);
9372
9373     if (!isIcsGame || whosays == GE_ICS) {
9374         /* OK -- not an ICS game, or ICS said it was done */
9375         StopClocks();
9376         if (!isIcsGame && !appData.noChessProgram)
9377           SetUserThinkingEnables();
9378
9379         /* [HGM] if a machine claims the game end we verify this claim */
9380         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9381             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9382                 char claimer;
9383                 ChessMove trueResult = (ChessMove) -1;
9384
9385                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9386                                             first.twoMachinesColor[0] :
9387                                             second.twoMachinesColor[0] ;
9388
9389                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9390                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9391                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9392                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9393                 } else
9394                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9395                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9396                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9397                 } else
9398                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9399                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9400                 }
9401
9402                 // now verify win claims, but not in drop games, as we don't understand those yet
9403                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9404                                                  || gameInfo.variant == VariantGreat) &&
9405                     (result == WhiteWins && claimer == 'w' ||
9406                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9407                       if (appData.debugMode) {
9408                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9409                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9410                       }
9411                       if(result != trueResult) {
9412                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9413                               result = claimer == 'w' ? BlackWins : WhiteWins;
9414                               resultDetails = buf;
9415                       }
9416                 } else
9417                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9418                     && (forwardMostMove <= backwardMostMove ||
9419                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9420                         (claimer=='b')==(forwardMostMove&1))
9421                                                                                   ) {
9422                       /* [HGM] verify: draws that were not flagged are false claims */
9423                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9424                       result = claimer == 'w' ? BlackWins : WhiteWins;
9425                       resultDetails = buf;
9426                 }
9427                 /* (Claiming a loss is accepted no questions asked!) */
9428             }
9429             /* [HGM] bare: don't allow bare King to win */
9430             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9431                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9432                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9433                && result != GameIsDrawn)
9434             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9435                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9436                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9437                         if(p >= 0 && p <= (int)WhiteKing) k++;
9438                 }
9439                 if (appData.debugMode) {
9440                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9441                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9442                 }
9443                 if(k <= 1) {
9444                         result = GameIsDrawn;
9445                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9446                         resultDetails = buf;
9447                 }
9448             }
9449         }
9450
9451
9452         if(serverMoves != NULL && !loadFlag) { char c = '=';
9453             if(result==WhiteWins) c = '+';
9454             if(result==BlackWins) c = '-';
9455             if(resultDetails != NULL)
9456                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9457         }
9458         if (resultDetails != NULL) {
9459             gameInfo.result = result;
9460             gameInfo.resultDetails = StrSave(resultDetails);
9461
9462             /* display last move only if game was not loaded from file */
9463             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9464                 DisplayMove(currentMove - 1);
9465
9466             if (forwardMostMove != 0) {
9467                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9468                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9469                                                                 ) {
9470                     if (*appData.saveGameFile != NULLCHAR) {
9471                         SaveGameToFile(appData.saveGameFile, TRUE);
9472                     } else if (appData.autoSaveGames) {
9473                         AutoSaveGame();
9474                     }
9475                     if (*appData.savePositionFile != NULLCHAR) {
9476                         SavePositionToFile(appData.savePositionFile);
9477                     }
9478                 }
9479             }
9480
9481             /* Tell program how game ended in case it is learning */
9482             /* [HGM] Moved this to after saving the PGN, just in case */
9483             /* engine died and we got here through time loss. In that */
9484             /* case we will get a fatal error writing the pipe, which */
9485             /* would otherwise lose us the PGN.                       */
9486             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9487             /* output during GameEnds should never be fatal anymore   */
9488             if (gameMode == MachinePlaysWhite ||
9489                 gameMode == MachinePlaysBlack ||
9490                 gameMode == TwoMachinesPlay ||
9491                 gameMode == IcsPlayingWhite ||
9492                 gameMode == IcsPlayingBlack ||
9493                 gameMode == BeginningOfGame) {
9494                 char buf[MSG_SIZ];
9495                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9496                         resultDetails);
9497                 if (first.pr != NoProc) {
9498                     SendToProgram(buf, &first);
9499                 }
9500                 if (second.pr != NoProc &&
9501                     gameMode == TwoMachinesPlay) {
9502                     SendToProgram(buf, &second);
9503                 }
9504             }
9505         }
9506
9507         if (appData.icsActive) {
9508             if (appData.quietPlay &&
9509                 (gameMode == IcsPlayingWhite ||
9510                  gameMode == IcsPlayingBlack)) {
9511                 SendToICS(ics_prefix);
9512                 SendToICS("set shout 1\n");
9513             }
9514             nextGameMode = IcsIdle;
9515             ics_user_moved = FALSE;
9516             /* clean up premove.  It's ugly when the game has ended and the
9517              * premove highlights are still on the board.
9518              */
9519             if (gotPremove) {
9520               gotPremove = FALSE;
9521               ClearPremoveHighlights();
9522               DrawPosition(FALSE, boards[currentMove]);
9523             }
9524             if (whosays == GE_ICS) {
9525                 switch (result) {
9526                 case WhiteWins:
9527                     if (gameMode == IcsPlayingWhite)
9528                         PlayIcsWinSound();
9529                     else if(gameMode == IcsPlayingBlack)
9530                         PlayIcsLossSound();
9531                     break;
9532                 case BlackWins:
9533                     if (gameMode == IcsPlayingBlack)
9534                         PlayIcsWinSound();
9535                     else if(gameMode == IcsPlayingWhite)
9536                         PlayIcsLossSound();
9537                     break;
9538                 case GameIsDrawn:
9539                     PlayIcsDrawSound();
9540                     break;
9541                 default:
9542                     PlayIcsUnfinishedSound();
9543                 }
9544             }
9545         } else if (gameMode == EditGame ||
9546                    gameMode == PlayFromGameFile ||
9547                    gameMode == AnalyzeMode ||
9548                    gameMode == AnalyzeFile) {
9549             nextGameMode = gameMode;
9550         } else {
9551             nextGameMode = EndOfGame;
9552         }
9553         pausing = FALSE;
9554         ModeHighlight();
9555     } else {
9556         nextGameMode = gameMode;
9557     }
9558
9559     if (appData.noChessProgram) {
9560         gameMode = nextGameMode;
9561         ModeHighlight();
9562         endingGame = 0; /* [HGM] crash */
9563         return;
9564     }
9565
9566     if (first.reuse) {
9567         /* Put first chess program into idle state */
9568         if (first.pr != NoProc &&
9569             (gameMode == MachinePlaysWhite ||
9570              gameMode == MachinePlaysBlack ||
9571              gameMode == TwoMachinesPlay ||
9572              gameMode == IcsPlayingWhite ||
9573              gameMode == IcsPlayingBlack ||
9574              gameMode == BeginningOfGame)) {
9575             SendToProgram("force\n", &first);
9576             if (first.usePing) {
9577               char buf[MSG_SIZ];
9578               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9579               SendToProgram(buf, &first);
9580             }
9581         }
9582     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9583         /* Kill off first chess program */
9584         if (first.isr != NULL)
9585           RemoveInputSource(first.isr);
9586         first.isr = NULL;
9587
9588         if (first.pr != NoProc) {
9589             ExitAnalyzeMode();
9590             DoSleep( appData.delayBeforeQuit );
9591             SendToProgram("quit\n", &first);
9592             DoSleep( appData.delayAfterQuit );
9593             DestroyChildProcess(first.pr, first.useSigterm);
9594         }
9595         first.pr = NoProc;
9596     }
9597     if (second.reuse) {
9598         /* Put second chess program into idle state */
9599         if (second.pr != NoProc &&
9600             gameMode == TwoMachinesPlay) {
9601             SendToProgram("force\n", &second);
9602             if (second.usePing) {
9603               char buf[MSG_SIZ];
9604               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9605               SendToProgram(buf, &second);
9606             }
9607         }
9608     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9609         /* Kill off second chess program */
9610         if (second.isr != NULL)
9611           RemoveInputSource(second.isr);
9612         second.isr = NULL;
9613
9614         if (second.pr != NoProc) {
9615             DoSleep( appData.delayBeforeQuit );
9616             SendToProgram("quit\n", &second);
9617             DoSleep( appData.delayAfterQuit );
9618             DestroyChildProcess(second.pr, second.useSigterm);
9619         }
9620         second.pr = NoProc;
9621     }
9622
9623     if (matchMode && gameMode == TwoMachinesPlay) {
9624         switch (result) {
9625         case WhiteWins:
9626           if (first.twoMachinesColor[0] == 'w') {
9627             first.matchWins++;
9628           } else {
9629             second.matchWins++;
9630           }
9631           break;
9632         case BlackWins:
9633           if (first.twoMachinesColor[0] == 'b') {
9634             first.matchWins++;
9635           } else {
9636             second.matchWins++;
9637           }
9638           break;
9639         default:
9640           break;
9641         }
9642         if (matchGame < appData.matchGames) {
9643             char *tmp;
9644             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9645                 tmp = first.twoMachinesColor;
9646                 first.twoMachinesColor = second.twoMachinesColor;
9647                 second.twoMachinesColor = tmp;
9648             }
9649             gameMode = nextGameMode;
9650             matchGame++;
9651             if(appData.matchPause>10000 || appData.matchPause<10)
9652                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9653             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9654             endingGame = 0; /* [HGM] crash */
9655             return;
9656         } else {
9657             gameMode = nextGameMode;
9658             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9659                      first.tidy, second.tidy,
9660                      first.matchWins, second.matchWins,
9661                      appData.matchGames - (first.matchWins + second.matchWins));
9662             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9663             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9664                 first.twoMachinesColor = "black\n";
9665                 second.twoMachinesColor = "white\n";
9666             } else {
9667                 first.twoMachinesColor = "white\n";
9668                 second.twoMachinesColor = "black\n";
9669             }
9670         }
9671     }
9672     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9673         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9674       ExitAnalyzeMode();
9675     gameMode = nextGameMode;
9676     ModeHighlight();
9677     endingGame = 0;  /* [HGM] crash */
9678     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9679       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9680         matchMode = FALSE; appData.matchGames = matchGame = 0;
9681         DisplayNote(buf);
9682       }
9683     }
9684 }
9685
9686 /* Assumes program was just initialized (initString sent).
9687    Leaves program in force mode. */
9688 void
9689 FeedMovesToProgram(cps, upto)
9690      ChessProgramState *cps;
9691      int upto;
9692 {
9693     int i;
9694
9695     if (appData.debugMode)
9696       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9697               startedFromSetupPosition ? "position and " : "",
9698               backwardMostMove, upto, cps->which);
9699     if(currentlyInitializedVariant != gameInfo.variant) {
9700       char buf[MSG_SIZ];
9701         // [HGM] variantswitch: make engine aware of new variant
9702         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9703                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9704         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9705         SendToProgram(buf, cps);
9706         currentlyInitializedVariant = gameInfo.variant;
9707     }
9708     SendToProgram("force\n", cps);
9709     if (startedFromSetupPosition) {
9710         SendBoard(cps, backwardMostMove);
9711     if (appData.debugMode) {
9712         fprintf(debugFP, "feedMoves\n");
9713     }
9714     }
9715     for (i = backwardMostMove; i < upto; i++) {
9716         SendMoveToProgram(i, cps);
9717     }
9718 }
9719
9720
9721 void
9722 ResurrectChessProgram()
9723 {
9724      /* The chess program may have exited.
9725         If so, restart it and feed it all the moves made so far. */
9726
9727     if (appData.noChessProgram || first.pr != NoProc) return;
9728
9729     StartChessProgram(&first);
9730     InitChessProgram(&first, FALSE);
9731     FeedMovesToProgram(&first, currentMove);
9732
9733     if (!first.sendTime) {
9734         /* can't tell gnuchess what its clock should read,
9735            so we bow to its notion. */
9736         ResetClocks();
9737         timeRemaining[0][currentMove] = whiteTimeRemaining;
9738         timeRemaining[1][currentMove] = blackTimeRemaining;
9739     }
9740
9741     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9742                 appData.icsEngineAnalyze) && first.analysisSupport) {
9743       SendToProgram("analyze\n", &first);
9744       first.analyzing = TRUE;
9745     }
9746 }
9747
9748 /*
9749  * Button procedures
9750  */
9751 void
9752 Reset(redraw, init)
9753      int redraw, init;
9754 {
9755     int i;
9756
9757     if (appData.debugMode) {
9758         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9759                 redraw, init, gameMode);
9760     }
9761     CleanupTail(); // [HGM] vari: delete any stored variations
9762     pausing = pauseExamInvalid = FALSE;
9763     startedFromSetupPosition = blackPlaysFirst = FALSE;
9764     firstMove = TRUE;
9765     whiteFlag = blackFlag = FALSE;
9766     userOfferedDraw = FALSE;
9767     hintRequested = bookRequested = FALSE;
9768     first.maybeThinking = FALSE;
9769     second.maybeThinking = FALSE;
9770     first.bookSuspend = FALSE; // [HGM] book
9771     second.bookSuspend = FALSE;
9772     thinkOutput[0] = NULLCHAR;
9773     lastHint[0] = NULLCHAR;
9774     ClearGameInfo(&gameInfo);
9775     gameInfo.variant = StringToVariant(appData.variant);
9776     ics_user_moved = ics_clock_paused = FALSE;
9777     ics_getting_history = H_FALSE;
9778     ics_gamenum = -1;
9779     white_holding[0] = black_holding[0] = NULLCHAR;
9780     ClearProgramStats();
9781     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9782
9783     ResetFrontEnd();
9784     ClearHighlights();
9785     flipView = appData.flipView;
9786     ClearPremoveHighlights();
9787     gotPremove = FALSE;
9788     alarmSounded = FALSE;
9789
9790     GameEnds(EndOfFile, NULL, GE_PLAYER);
9791     if(appData.serverMovesName != NULL) {
9792         /* [HGM] prepare to make moves file for broadcasting */
9793         clock_t t = clock();
9794         if(serverMoves != NULL) fclose(serverMoves);
9795         serverMoves = fopen(appData.serverMovesName, "r");
9796         if(serverMoves != NULL) {
9797             fclose(serverMoves);
9798             /* delay 15 sec before overwriting, so all clients can see end */
9799             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9800         }
9801         serverMoves = fopen(appData.serverMovesName, "w");
9802     }
9803
9804     ExitAnalyzeMode();
9805     gameMode = BeginningOfGame;
9806     ModeHighlight();
9807     if(appData.icsActive) gameInfo.variant = VariantNormal;
9808     currentMove = forwardMostMove = backwardMostMove = 0;
9809     InitPosition(redraw);
9810     for (i = 0; i < MAX_MOVES; i++) {
9811         if (commentList[i] != NULL) {
9812             free(commentList[i]);
9813             commentList[i] = NULL;
9814         }
9815     }
9816     ResetClocks();
9817     timeRemaining[0][0] = whiteTimeRemaining;
9818     timeRemaining[1][0] = blackTimeRemaining;
9819     if (first.pr == NULL) {
9820         StartChessProgram(&first);
9821     }
9822     if (init) {
9823             InitChessProgram(&first, startedFromSetupPosition);
9824     }
9825     DisplayTitle("");
9826     DisplayMessage("", "");
9827     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9828     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9829 }
9830
9831 void
9832 AutoPlayGameLoop()
9833 {
9834     for (;;) {
9835         if (!AutoPlayOneMove())
9836           return;
9837         if (matchMode || appData.timeDelay == 0)
9838           continue;
9839         if (appData.timeDelay < 0)
9840           return;
9841         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9842         break;
9843     }
9844 }
9845
9846
9847 int
9848 AutoPlayOneMove()
9849 {
9850     int fromX, fromY, toX, toY;
9851
9852     if (appData.debugMode) {
9853       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9854     }
9855
9856     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9857       return FALSE;
9858
9859     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9860       pvInfoList[currentMove].depth = programStats.depth;
9861       pvInfoList[currentMove].score = programStats.score;
9862       pvInfoList[currentMove].time  = 0;
9863       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9864     }
9865
9866     if (currentMove >= forwardMostMove) {
9867       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9868       gameMode = EditGame;
9869       ModeHighlight();
9870
9871       /* [AS] Clear current move marker at the end of a game */
9872       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9873
9874       return FALSE;
9875     }
9876
9877     toX = moveList[currentMove][2] - AAA;
9878     toY = moveList[currentMove][3] - ONE;
9879
9880     if (moveList[currentMove][1] == '@') {
9881         if (appData.highlightLastMove) {
9882             SetHighlights(-1, -1, toX, toY);
9883         }
9884     } else {
9885         fromX = moveList[currentMove][0] - AAA;
9886         fromY = moveList[currentMove][1] - ONE;
9887
9888         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9889
9890         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9891
9892         if (appData.highlightLastMove) {
9893             SetHighlights(fromX, fromY, toX, toY);
9894         }
9895     }
9896     DisplayMove(currentMove);
9897     SendMoveToProgram(currentMove++, &first);
9898     DisplayBothClocks();
9899     DrawPosition(FALSE, boards[currentMove]);
9900     // [HGM] PV info: always display, routine tests if empty
9901     DisplayComment(currentMove - 1, commentList[currentMove]);
9902     return TRUE;
9903 }
9904
9905
9906 int
9907 LoadGameOneMove(readAhead)
9908      ChessMove readAhead;
9909 {
9910     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9911     char promoChar = NULLCHAR;
9912     ChessMove moveType;
9913     char move[MSG_SIZ];
9914     char *p, *q;
9915
9916     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9917         gameMode != AnalyzeMode && gameMode != Training) {
9918         gameFileFP = NULL;
9919         return FALSE;
9920     }
9921
9922     yyboardindex = forwardMostMove;
9923     if (readAhead != EndOfFile) {
9924       moveType = readAhead;
9925     } else {
9926       if (gameFileFP == NULL)
9927           return FALSE;
9928       moveType = (ChessMove) Myylex();
9929     }
9930
9931     done = FALSE;
9932     switch (moveType) {
9933       case Comment:
9934         if (appData.debugMode)
9935           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9936         p = yy_text;
9937
9938         /* append the comment but don't display it */
9939         AppendComment(currentMove, p, FALSE);
9940         return TRUE;
9941
9942       case WhiteCapturesEnPassant:
9943       case BlackCapturesEnPassant:
9944       case WhitePromotion:
9945       case BlackPromotion:
9946       case WhiteNonPromotion:
9947       case BlackNonPromotion:
9948       case NormalMove:
9949       case WhiteKingSideCastle:
9950       case WhiteQueenSideCastle:
9951       case BlackKingSideCastle:
9952       case BlackQueenSideCastle:
9953       case WhiteKingSideCastleWild:
9954       case WhiteQueenSideCastleWild:
9955       case BlackKingSideCastleWild:
9956       case BlackQueenSideCastleWild:
9957       /* PUSH Fabien */
9958       case WhiteHSideCastleFR:
9959       case WhiteASideCastleFR:
9960       case BlackHSideCastleFR:
9961       case BlackASideCastleFR:
9962       /* POP Fabien */
9963         if (appData.debugMode)
9964           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9965         fromX = currentMoveString[0] - AAA;
9966         fromY = currentMoveString[1] - ONE;
9967         toX = currentMoveString[2] - AAA;
9968         toY = currentMoveString[3] - ONE;
9969         promoChar = currentMoveString[4];
9970         break;
9971
9972       case WhiteDrop:
9973       case BlackDrop:
9974         if (appData.debugMode)
9975           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9976         fromX = moveType == WhiteDrop ?
9977           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9978         (int) CharToPiece(ToLower(currentMoveString[0]));
9979         fromY = DROP_RANK;
9980         toX = currentMoveString[2] - AAA;
9981         toY = currentMoveString[3] - ONE;
9982         break;
9983
9984       case WhiteWins:
9985       case BlackWins:
9986       case GameIsDrawn:
9987       case GameUnfinished:
9988         if (appData.debugMode)
9989           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9990         p = strchr(yy_text, '{');
9991         if (p == NULL) p = strchr(yy_text, '(');
9992         if (p == NULL) {
9993             p = yy_text;
9994             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9995         } else {
9996             q = strchr(p, *p == '{' ? '}' : ')');
9997             if (q != NULL) *q = NULLCHAR;
9998             p++;
9999         }
10000         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10001         GameEnds(moveType, p, GE_FILE);
10002         done = TRUE;
10003         if (cmailMsgLoaded) {
10004             ClearHighlights();
10005             flipView = WhiteOnMove(currentMove);
10006             if (moveType == GameUnfinished) flipView = !flipView;
10007             if (appData.debugMode)
10008               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10009         }
10010         break;
10011
10012       case EndOfFile:
10013         if (appData.debugMode)
10014           fprintf(debugFP, "Parser hit end of file\n");
10015         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10016           case MT_NONE:
10017           case MT_CHECK:
10018             break;
10019           case MT_CHECKMATE:
10020           case MT_STAINMATE:
10021             if (WhiteOnMove(currentMove)) {
10022                 GameEnds(BlackWins, "Black mates", GE_FILE);
10023             } else {
10024                 GameEnds(WhiteWins, "White mates", GE_FILE);
10025             }
10026             break;
10027           case MT_STALEMATE:
10028             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10029             break;
10030         }
10031         done = TRUE;
10032         break;
10033
10034       case MoveNumberOne:
10035         if (lastLoadGameStart == GNUChessGame) {
10036             /* GNUChessGames have numbers, but they aren't move numbers */
10037             if (appData.debugMode)
10038               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10039                       yy_text, (int) moveType);
10040             return LoadGameOneMove(EndOfFile); /* tail recursion */
10041         }
10042         /* else fall thru */
10043
10044       case XBoardGame:
10045       case GNUChessGame:
10046       case PGNTag:
10047         /* Reached start of next game in file */
10048         if (appData.debugMode)
10049           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10050         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10051           case MT_NONE:
10052           case MT_CHECK:
10053             break;
10054           case MT_CHECKMATE:
10055           case MT_STAINMATE:
10056             if (WhiteOnMove(currentMove)) {
10057                 GameEnds(BlackWins, "Black mates", GE_FILE);
10058             } else {
10059                 GameEnds(WhiteWins, "White mates", GE_FILE);
10060             }
10061             break;
10062           case MT_STALEMATE:
10063             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10064             break;
10065         }
10066         done = TRUE;
10067         break;
10068
10069       case PositionDiagram:     /* should not happen; ignore */
10070       case ElapsedTime:         /* ignore */
10071       case NAG:                 /* ignore */
10072         if (appData.debugMode)
10073           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10074                   yy_text, (int) moveType);
10075         return LoadGameOneMove(EndOfFile); /* tail recursion */
10076
10077       case IllegalMove:
10078         if (appData.testLegality) {
10079             if (appData.debugMode)
10080               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10081             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10082                     (forwardMostMove / 2) + 1,
10083                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10084             DisplayError(move, 0);
10085             done = TRUE;
10086         } else {
10087             if (appData.debugMode)
10088               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10089                       yy_text, currentMoveString);
10090             fromX = currentMoveString[0] - AAA;
10091             fromY = currentMoveString[1] - ONE;
10092             toX = currentMoveString[2] - AAA;
10093             toY = currentMoveString[3] - ONE;
10094             promoChar = currentMoveString[4];
10095         }
10096         break;
10097
10098       case AmbiguousMove:
10099         if (appData.debugMode)
10100           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10101         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10102                 (forwardMostMove / 2) + 1,
10103                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10104         DisplayError(move, 0);
10105         done = TRUE;
10106         break;
10107
10108       default:
10109       case ImpossibleMove:
10110         if (appData.debugMode)
10111           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10112         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10113                 (forwardMostMove / 2) + 1,
10114                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10115         DisplayError(move, 0);
10116         done = TRUE;
10117         break;
10118     }
10119
10120     if (done) {
10121         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10122             DrawPosition(FALSE, boards[currentMove]);
10123             DisplayBothClocks();
10124             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10125               DisplayComment(currentMove - 1, commentList[currentMove]);
10126         }
10127         (void) StopLoadGameTimer();
10128         gameFileFP = NULL;
10129         cmailOldMove = forwardMostMove;
10130         return FALSE;
10131     } else {
10132         /* currentMoveString is set as a side-effect of yylex */
10133
10134         thinkOutput[0] = NULLCHAR;
10135         MakeMove(fromX, fromY, toX, toY, promoChar);
10136         currentMove = forwardMostMove;
10137         return TRUE;
10138     }
10139 }
10140
10141 /* Load the nth game from the given file */
10142 int
10143 LoadGameFromFile(filename, n, title, useList)
10144      char *filename;
10145      int n;
10146      char *title;
10147      /*Boolean*/ int useList;
10148 {
10149     FILE *f;
10150     char buf[MSG_SIZ];
10151
10152     if (strcmp(filename, "-") == 0) {
10153         f = stdin;
10154         title = "stdin";
10155     } else {
10156         f = fopen(filename, "rb");
10157         if (f == NULL) {
10158           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10159             DisplayError(buf, errno);
10160             return FALSE;
10161         }
10162     }
10163     if (fseek(f, 0, 0) == -1) {
10164         /* f is not seekable; probably a pipe */
10165         useList = FALSE;
10166     }
10167     if (useList && n == 0) {
10168         int error = GameListBuild(f);
10169         if (error) {
10170             DisplayError(_("Cannot build game list"), error);
10171         } else if (!ListEmpty(&gameList) &&
10172                    ((ListGame *) gameList.tailPred)->number > 1) {
10173             GameListPopUp(f, title);
10174             return TRUE;
10175         }
10176         GameListDestroy();
10177         n = 1;
10178     }
10179     if (n == 0) n = 1;
10180     return LoadGame(f, n, title, FALSE);
10181 }
10182
10183
10184 void
10185 MakeRegisteredMove()
10186 {
10187     int fromX, fromY, toX, toY;
10188     char promoChar;
10189     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10190         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10191           case CMAIL_MOVE:
10192           case CMAIL_DRAW:
10193             if (appData.debugMode)
10194               fprintf(debugFP, "Restoring %s for game %d\n",
10195                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10196
10197             thinkOutput[0] = NULLCHAR;
10198             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10199             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10200             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10201             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10202             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10203             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10204             MakeMove(fromX, fromY, toX, toY, promoChar);
10205             ShowMove(fromX, fromY, toX, toY);
10206
10207             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10208               case MT_NONE:
10209               case MT_CHECK:
10210                 break;
10211
10212               case MT_CHECKMATE:
10213               case MT_STAINMATE:
10214                 if (WhiteOnMove(currentMove)) {
10215                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10216                 } else {
10217                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10218                 }
10219                 break;
10220
10221               case MT_STALEMATE:
10222                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10223                 break;
10224             }
10225
10226             break;
10227
10228           case CMAIL_RESIGN:
10229             if (WhiteOnMove(currentMove)) {
10230                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10231             } else {
10232                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10233             }
10234             break;
10235
10236           case CMAIL_ACCEPT:
10237             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10238             break;
10239
10240           default:
10241             break;
10242         }
10243     }
10244
10245     return;
10246 }
10247
10248 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10249 int
10250 CmailLoadGame(f, gameNumber, title, useList)
10251      FILE *f;
10252      int gameNumber;
10253      char *title;
10254      int useList;
10255 {
10256     int retVal;
10257
10258     if (gameNumber > nCmailGames) {
10259         DisplayError(_("No more games in this message"), 0);
10260         return FALSE;
10261     }
10262     if (f == lastLoadGameFP) {
10263         int offset = gameNumber - lastLoadGameNumber;
10264         if (offset == 0) {
10265             cmailMsg[0] = NULLCHAR;
10266             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10267                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10268                 nCmailMovesRegistered--;
10269             }
10270             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10271             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10272                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10273             }
10274         } else {
10275             if (! RegisterMove()) return FALSE;
10276         }
10277     }
10278
10279     retVal = LoadGame(f, gameNumber, title, useList);
10280
10281     /* Make move registered during previous look at this game, if any */
10282     MakeRegisteredMove();
10283
10284     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10285         commentList[currentMove]
10286           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10287         DisplayComment(currentMove - 1, commentList[currentMove]);
10288     }
10289
10290     return retVal;
10291 }
10292
10293 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10294 int
10295 ReloadGame(offset)
10296      int offset;
10297 {
10298     int gameNumber = lastLoadGameNumber + offset;
10299     if (lastLoadGameFP == NULL) {
10300         DisplayError(_("No game has been loaded yet"), 0);
10301         return FALSE;
10302     }
10303     if (gameNumber <= 0) {
10304         DisplayError(_("Can't back up any further"), 0);
10305         return FALSE;
10306     }
10307     if (cmailMsgLoaded) {
10308         return CmailLoadGame(lastLoadGameFP, gameNumber,
10309                              lastLoadGameTitle, lastLoadGameUseList);
10310     } else {
10311         return LoadGame(lastLoadGameFP, gameNumber,
10312                         lastLoadGameTitle, lastLoadGameUseList);
10313     }
10314 }
10315
10316
10317
10318 /* Load the nth game from open file f */
10319 int
10320 LoadGame(f, gameNumber, title, useList)
10321      FILE *f;
10322      int gameNumber;
10323      char *title;
10324      int useList;
10325 {
10326     ChessMove cm;
10327     char buf[MSG_SIZ];
10328     int gn = gameNumber;
10329     ListGame *lg = NULL;
10330     int numPGNTags = 0;
10331     int err;
10332     GameMode oldGameMode;
10333     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10334
10335     if (appData.debugMode)
10336         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10337
10338     if (gameMode == Training )
10339         SetTrainingModeOff();
10340
10341     oldGameMode = gameMode;
10342     if (gameMode != BeginningOfGame) {
10343       Reset(FALSE, TRUE);
10344     }
10345
10346     gameFileFP = f;
10347     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10348         fclose(lastLoadGameFP);
10349     }
10350
10351     if (useList) {
10352         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10353
10354         if (lg) {
10355             fseek(f, lg->offset, 0);
10356             GameListHighlight(gameNumber);
10357             gn = 1;
10358         }
10359         else {
10360             DisplayError(_("Game number out of range"), 0);
10361             return FALSE;
10362         }
10363     } else {
10364         GameListDestroy();
10365         if (fseek(f, 0, 0) == -1) {
10366             if (f == lastLoadGameFP ?
10367                 gameNumber == lastLoadGameNumber + 1 :
10368                 gameNumber == 1) {
10369                 gn = 1;
10370             } else {
10371                 DisplayError(_("Can't seek on game file"), 0);
10372                 return FALSE;
10373             }
10374         }
10375     }
10376     lastLoadGameFP = f;
10377     lastLoadGameNumber = gameNumber;
10378     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10379     lastLoadGameUseList = useList;
10380
10381     yynewfile(f);
10382
10383     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10384       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10385                 lg->gameInfo.black);
10386             DisplayTitle(buf);
10387     } else if (*title != NULLCHAR) {
10388         if (gameNumber > 1) {
10389           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10390             DisplayTitle(buf);
10391         } else {
10392             DisplayTitle(title);
10393         }
10394     }
10395
10396     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10397         gameMode = PlayFromGameFile;
10398         ModeHighlight();
10399     }
10400
10401     currentMove = forwardMostMove = backwardMostMove = 0;
10402     CopyBoard(boards[0], initialPosition);
10403     StopClocks();
10404
10405     /*
10406      * Skip the first gn-1 games in the file.
10407      * Also skip over anything that precedes an identifiable
10408      * start of game marker, to avoid being confused by
10409      * garbage at the start of the file.  Currently
10410      * recognized start of game markers are the move number "1",
10411      * the pattern "gnuchess .* game", the pattern
10412      * "^[#;%] [^ ]* game file", and a PGN tag block.
10413      * A game that starts with one of the latter two patterns
10414      * will also have a move number 1, possibly
10415      * following a position diagram.
10416      * 5-4-02: Let's try being more lenient and allowing a game to
10417      * start with an unnumbered move.  Does that break anything?
10418      */
10419     cm = lastLoadGameStart = EndOfFile;
10420     while (gn > 0) {
10421         yyboardindex = forwardMostMove;
10422         cm = (ChessMove) Myylex();
10423         switch (cm) {
10424           case EndOfFile:
10425             if (cmailMsgLoaded) {
10426                 nCmailGames = CMAIL_MAX_GAMES - gn;
10427             } else {
10428                 Reset(TRUE, TRUE);
10429                 DisplayError(_("Game not found in file"), 0);
10430             }
10431             return FALSE;
10432
10433           case GNUChessGame:
10434           case XBoardGame:
10435             gn--;
10436             lastLoadGameStart = cm;
10437             break;
10438
10439           case MoveNumberOne:
10440             switch (lastLoadGameStart) {
10441               case GNUChessGame:
10442               case XBoardGame:
10443               case PGNTag:
10444                 break;
10445               case MoveNumberOne:
10446               case EndOfFile:
10447                 gn--;           /* count this game */
10448                 lastLoadGameStart = cm;
10449                 break;
10450               default:
10451                 /* impossible */
10452                 break;
10453             }
10454             break;
10455
10456           case PGNTag:
10457             switch (lastLoadGameStart) {
10458               case GNUChessGame:
10459               case PGNTag:
10460               case MoveNumberOne:
10461               case EndOfFile:
10462                 gn--;           /* count this game */
10463                 lastLoadGameStart = cm;
10464                 break;
10465               case XBoardGame:
10466                 lastLoadGameStart = cm; /* game counted already */
10467                 break;
10468               default:
10469                 /* impossible */
10470                 break;
10471             }
10472             if (gn > 0) {
10473                 do {
10474                     yyboardindex = forwardMostMove;
10475                     cm = (ChessMove) Myylex();
10476                 } while (cm == PGNTag || cm == Comment);
10477             }
10478             break;
10479
10480           case WhiteWins:
10481           case BlackWins:
10482           case GameIsDrawn:
10483             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10484                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10485                     != CMAIL_OLD_RESULT) {
10486                     nCmailResults ++ ;
10487                     cmailResult[  CMAIL_MAX_GAMES
10488                                 - gn - 1] = CMAIL_OLD_RESULT;
10489                 }
10490             }
10491             break;
10492
10493           case NormalMove:
10494             /* Only a NormalMove can be at the start of a game
10495              * without a position diagram. */
10496             if (lastLoadGameStart == EndOfFile ) {
10497               gn--;
10498               lastLoadGameStart = MoveNumberOne;
10499             }
10500             break;
10501
10502           default:
10503             break;
10504         }
10505     }
10506
10507     if (appData.debugMode)
10508       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10509
10510     if (cm == XBoardGame) {
10511         /* Skip any header junk before position diagram and/or move 1 */
10512         for (;;) {
10513             yyboardindex = forwardMostMove;
10514             cm = (ChessMove) Myylex();
10515
10516             if (cm == EndOfFile ||
10517                 cm == GNUChessGame || cm == XBoardGame) {
10518                 /* Empty game; pretend end-of-file and handle later */
10519                 cm = EndOfFile;
10520                 break;
10521             }
10522
10523             if (cm == MoveNumberOne || cm == PositionDiagram ||
10524                 cm == PGNTag || cm == Comment)
10525               break;
10526         }
10527     } else if (cm == GNUChessGame) {
10528         if (gameInfo.event != NULL) {
10529             free(gameInfo.event);
10530         }
10531         gameInfo.event = StrSave(yy_text);
10532     }
10533
10534     startedFromSetupPosition = FALSE;
10535     while (cm == PGNTag) {
10536         if (appData.debugMode)
10537           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10538         err = ParsePGNTag(yy_text, &gameInfo);
10539         if (!err) numPGNTags++;
10540
10541         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10542         if(gameInfo.variant != oldVariant) {
10543             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10544             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10545             InitPosition(TRUE);
10546             oldVariant = gameInfo.variant;
10547             if (appData.debugMode)
10548               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10549         }
10550
10551
10552         if (gameInfo.fen != NULL) {
10553           Board initial_position;
10554           startedFromSetupPosition = TRUE;
10555           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10556             Reset(TRUE, TRUE);
10557             DisplayError(_("Bad FEN position in file"), 0);
10558             return FALSE;
10559           }
10560           CopyBoard(boards[0], initial_position);
10561           if (blackPlaysFirst) {
10562             currentMove = forwardMostMove = backwardMostMove = 1;
10563             CopyBoard(boards[1], initial_position);
10564             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10565             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10566             timeRemaining[0][1] = whiteTimeRemaining;
10567             timeRemaining[1][1] = blackTimeRemaining;
10568             if (commentList[0] != NULL) {
10569               commentList[1] = commentList[0];
10570               commentList[0] = NULL;
10571             }
10572           } else {
10573             currentMove = forwardMostMove = backwardMostMove = 0;
10574           }
10575           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10576           {   int i;
10577               initialRulePlies = FENrulePlies;
10578               for( i=0; i< nrCastlingRights; i++ )
10579                   initialRights[i] = initial_position[CASTLING][i];
10580           }
10581           yyboardindex = forwardMostMove;
10582           free(gameInfo.fen);
10583           gameInfo.fen = NULL;
10584         }
10585
10586         yyboardindex = forwardMostMove;
10587         cm = (ChessMove) Myylex();
10588
10589         /* Handle comments interspersed among the tags */
10590         while (cm == Comment) {
10591             char *p;
10592             if (appData.debugMode)
10593               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10594             p = yy_text;
10595             AppendComment(currentMove, p, FALSE);
10596             yyboardindex = forwardMostMove;
10597             cm = (ChessMove) Myylex();
10598         }
10599     }
10600
10601     /* don't rely on existence of Event tag since if game was
10602      * pasted from clipboard the Event tag may not exist
10603      */
10604     if (numPGNTags > 0){
10605         char *tags;
10606         if (gameInfo.variant == VariantNormal) {
10607           VariantClass v = StringToVariant(gameInfo.event);
10608           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10609           if(v < VariantShogi) gameInfo.variant = v;
10610         }
10611         if (!matchMode) {
10612           if( appData.autoDisplayTags ) {
10613             tags = PGNTags(&gameInfo);
10614             TagsPopUp(tags, CmailMsg());
10615             free(tags);
10616           }
10617         }
10618     } else {
10619         /* Make something up, but don't display it now */
10620         SetGameInfo();
10621         TagsPopDown();
10622     }
10623
10624     if (cm == PositionDiagram) {
10625         int i, j;
10626         char *p;
10627         Board initial_position;
10628
10629         if (appData.debugMode)
10630           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10631
10632         if (!startedFromSetupPosition) {
10633             p = yy_text;
10634             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10635               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10636                 switch (*p) {
10637                   case '{':
10638                   case '[':
10639                   case '-':
10640                   case ' ':
10641                   case '\t':
10642                   case '\n':
10643                   case '\r':
10644                     break;
10645                   default:
10646                     initial_position[i][j++] = CharToPiece(*p);
10647                     break;
10648                 }
10649             while (*p == ' ' || *p == '\t' ||
10650                    *p == '\n' || *p == '\r') p++;
10651
10652             if (strncmp(p, "black", strlen("black"))==0)
10653               blackPlaysFirst = TRUE;
10654             else
10655               blackPlaysFirst = FALSE;
10656             startedFromSetupPosition = TRUE;
10657
10658             CopyBoard(boards[0], initial_position);
10659             if (blackPlaysFirst) {
10660                 currentMove = forwardMostMove = backwardMostMove = 1;
10661                 CopyBoard(boards[1], initial_position);
10662                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10663                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10664                 timeRemaining[0][1] = whiteTimeRemaining;
10665                 timeRemaining[1][1] = blackTimeRemaining;
10666                 if (commentList[0] != NULL) {
10667                     commentList[1] = commentList[0];
10668                     commentList[0] = NULL;
10669                 }
10670             } else {
10671                 currentMove = forwardMostMove = backwardMostMove = 0;
10672             }
10673         }
10674         yyboardindex = forwardMostMove;
10675         cm = (ChessMove) Myylex();
10676     }
10677
10678     if (first.pr == NoProc) {
10679         StartChessProgram(&first);
10680     }
10681     InitChessProgram(&first, FALSE);
10682     SendToProgram("force\n", &first);
10683     if (startedFromSetupPosition) {
10684         SendBoard(&first, forwardMostMove);
10685     if (appData.debugMode) {
10686         fprintf(debugFP, "Load Game\n");
10687     }
10688         DisplayBothClocks();
10689     }
10690
10691     /* [HGM] server: flag to write setup moves in broadcast file as one */
10692     loadFlag = appData.suppressLoadMoves;
10693
10694     while (cm == Comment) {
10695         char *p;
10696         if (appData.debugMode)
10697           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10698         p = yy_text;
10699         AppendComment(currentMove, p, FALSE);
10700         yyboardindex = forwardMostMove;
10701         cm = (ChessMove) Myylex();
10702     }
10703
10704     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10705         cm == WhiteWins || cm == BlackWins ||
10706         cm == GameIsDrawn || cm == GameUnfinished) {
10707         DisplayMessage("", _("No moves in game"));
10708         if (cmailMsgLoaded) {
10709             if (appData.debugMode)
10710               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10711             ClearHighlights();
10712             flipView = FALSE;
10713         }
10714         DrawPosition(FALSE, boards[currentMove]);
10715         DisplayBothClocks();
10716         gameMode = EditGame;
10717         ModeHighlight();
10718         gameFileFP = NULL;
10719         cmailOldMove = 0;
10720         return TRUE;
10721     }
10722
10723     // [HGM] PV info: routine tests if comment empty
10724     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10725         DisplayComment(currentMove - 1, commentList[currentMove]);
10726     }
10727     if (!matchMode && appData.timeDelay != 0)
10728       DrawPosition(FALSE, boards[currentMove]);
10729
10730     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10731       programStats.ok_to_send = 1;
10732     }
10733
10734     /* if the first token after the PGN tags is a move
10735      * and not move number 1, retrieve it from the parser
10736      */
10737     if (cm != MoveNumberOne)
10738         LoadGameOneMove(cm);
10739
10740     /* load the remaining moves from the file */
10741     while (LoadGameOneMove(EndOfFile)) {
10742       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10743       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10744     }
10745
10746     /* rewind to the start of the game */
10747     currentMove = backwardMostMove;
10748
10749     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10750
10751     if (oldGameMode == AnalyzeFile ||
10752         oldGameMode == AnalyzeMode) {
10753       AnalyzeFileEvent();
10754     }
10755
10756     if (matchMode || appData.timeDelay == 0) {
10757       ToEndEvent();
10758       gameMode = EditGame;
10759       ModeHighlight();
10760     } else if (appData.timeDelay > 0) {
10761       AutoPlayGameLoop();
10762     }
10763
10764     if (appData.debugMode)
10765         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10766
10767     loadFlag = 0; /* [HGM] true game starts */
10768     return TRUE;
10769 }
10770
10771 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10772 int
10773 ReloadPosition(offset)
10774      int offset;
10775 {
10776     int positionNumber = lastLoadPositionNumber + offset;
10777     if (lastLoadPositionFP == NULL) {
10778         DisplayError(_("No position has been loaded yet"), 0);
10779         return FALSE;
10780     }
10781     if (positionNumber <= 0) {
10782         DisplayError(_("Can't back up any further"), 0);
10783         return FALSE;
10784     }
10785     return LoadPosition(lastLoadPositionFP, positionNumber,
10786                         lastLoadPositionTitle);
10787 }
10788
10789 /* Load the nth position from the given file */
10790 int
10791 LoadPositionFromFile(filename, n, title)
10792      char *filename;
10793      int n;
10794      char *title;
10795 {
10796     FILE *f;
10797     char buf[MSG_SIZ];
10798
10799     if (strcmp(filename, "-") == 0) {
10800         return LoadPosition(stdin, n, "stdin");
10801     } else {
10802         f = fopen(filename, "rb");
10803         if (f == NULL) {
10804             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10805             DisplayError(buf, errno);
10806             return FALSE;
10807         } else {
10808             return LoadPosition(f, n, title);
10809         }
10810     }
10811 }
10812
10813 /* Load the nth position from the given open file, and close it */
10814 int
10815 LoadPosition(f, positionNumber, title)
10816      FILE *f;
10817      int positionNumber;
10818      char *title;
10819 {
10820     char *p, line[MSG_SIZ];
10821     Board initial_position;
10822     int i, j, fenMode, pn;
10823
10824     if (gameMode == Training )
10825         SetTrainingModeOff();
10826
10827     if (gameMode != BeginningOfGame) {
10828         Reset(FALSE, TRUE);
10829     }
10830     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10831         fclose(lastLoadPositionFP);
10832     }
10833     if (positionNumber == 0) positionNumber = 1;
10834     lastLoadPositionFP = f;
10835     lastLoadPositionNumber = positionNumber;
10836     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10837     if (first.pr == NoProc) {
10838       StartChessProgram(&first);
10839       InitChessProgram(&first, FALSE);
10840     }
10841     pn = positionNumber;
10842     if (positionNumber < 0) {
10843         /* Negative position number means to seek to that byte offset */
10844         if (fseek(f, -positionNumber, 0) == -1) {
10845             DisplayError(_("Can't seek on position file"), 0);
10846             return FALSE;
10847         };
10848         pn = 1;
10849     } else {
10850         if (fseek(f, 0, 0) == -1) {
10851             if (f == lastLoadPositionFP ?
10852                 positionNumber == lastLoadPositionNumber + 1 :
10853                 positionNumber == 1) {
10854                 pn = 1;
10855             } else {
10856                 DisplayError(_("Can't seek on position file"), 0);
10857                 return FALSE;
10858             }
10859         }
10860     }
10861     /* See if this file is FEN or old-style xboard */
10862     if (fgets(line, MSG_SIZ, f) == NULL) {
10863         DisplayError(_("Position not found in file"), 0);
10864         return FALSE;
10865     }
10866     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10867     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10868
10869     if (pn >= 2) {
10870         if (fenMode || line[0] == '#') pn--;
10871         while (pn > 0) {
10872             /* skip positions before number pn */
10873             if (fgets(line, MSG_SIZ, f) == NULL) {
10874                 Reset(TRUE, TRUE);
10875                 DisplayError(_("Position not found in file"), 0);
10876                 return FALSE;
10877             }
10878             if (fenMode || line[0] == '#') pn--;
10879         }
10880     }
10881
10882     if (fenMode) {
10883         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10884             DisplayError(_("Bad FEN position in file"), 0);
10885             return FALSE;
10886         }
10887     } else {
10888         (void) fgets(line, MSG_SIZ, f);
10889         (void) fgets(line, MSG_SIZ, f);
10890
10891         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10892             (void) fgets(line, MSG_SIZ, f);
10893             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10894                 if (*p == ' ')
10895                   continue;
10896                 initial_position[i][j++] = CharToPiece(*p);
10897             }
10898         }
10899
10900         blackPlaysFirst = FALSE;
10901         if (!feof(f)) {
10902             (void) fgets(line, MSG_SIZ, f);
10903             if (strncmp(line, "black", strlen("black"))==0)
10904               blackPlaysFirst = TRUE;
10905         }
10906     }
10907     startedFromSetupPosition = TRUE;
10908
10909     SendToProgram("force\n", &first);
10910     CopyBoard(boards[0], initial_position);
10911     if (blackPlaysFirst) {
10912         currentMove = forwardMostMove = backwardMostMove = 1;
10913         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10914         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10915         CopyBoard(boards[1], initial_position);
10916         DisplayMessage("", _("Black to play"));
10917     } else {
10918         currentMove = forwardMostMove = backwardMostMove = 0;
10919         DisplayMessage("", _("White to play"));
10920     }
10921     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10922     SendBoard(&first, forwardMostMove);
10923     if (appData.debugMode) {
10924 int i, j;
10925   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10926   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10927         fprintf(debugFP, "Load Position\n");
10928     }
10929
10930     if (positionNumber > 1) {
10931       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10932         DisplayTitle(line);
10933     } else {
10934         DisplayTitle(title);
10935     }
10936     gameMode = EditGame;
10937     ModeHighlight();
10938     ResetClocks();
10939     timeRemaining[0][1] = whiteTimeRemaining;
10940     timeRemaining[1][1] = blackTimeRemaining;
10941     DrawPosition(FALSE, boards[currentMove]);
10942
10943     return TRUE;
10944 }
10945
10946
10947 void
10948 CopyPlayerNameIntoFileName(dest, src)
10949      char **dest, *src;
10950 {
10951     while (*src != NULLCHAR && *src != ',') {
10952         if (*src == ' ') {
10953             *(*dest)++ = '_';
10954             src++;
10955         } else {
10956             *(*dest)++ = *src++;
10957         }
10958     }
10959 }
10960
10961 char *DefaultFileName(ext)
10962      char *ext;
10963 {
10964     static char def[MSG_SIZ];
10965     char *p;
10966
10967     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10968         p = def;
10969         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10970         *p++ = '-';
10971         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10972         *p++ = '.';
10973         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10974     } else {
10975         def[0] = NULLCHAR;
10976     }
10977     return def;
10978 }
10979
10980 /* Save the current game to the given file */
10981 int
10982 SaveGameToFile(filename, append)
10983      char *filename;
10984      int append;
10985 {
10986     FILE *f;
10987     char buf[MSG_SIZ];
10988
10989     if (strcmp(filename, "-") == 0) {
10990         return SaveGame(stdout, 0, NULL);
10991     } else {
10992         f = fopen(filename, append ? "a" : "w");
10993         if (f == NULL) {
10994             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10995             DisplayError(buf, errno);
10996             return FALSE;
10997         } else {
10998             return SaveGame(f, 0, NULL);
10999         }
11000     }
11001 }
11002
11003 char *
11004 SavePart(str)
11005      char *str;
11006 {
11007     static char buf[MSG_SIZ];
11008     char *p;
11009
11010     p = strchr(str, ' ');
11011     if (p == NULL) return str;
11012     strncpy(buf, str, p - str);
11013     buf[p - str] = NULLCHAR;
11014     return buf;
11015 }
11016
11017 #define PGN_MAX_LINE 75
11018
11019 #define PGN_SIDE_WHITE  0
11020 #define PGN_SIDE_BLACK  1
11021
11022 /* [AS] */
11023 static int FindFirstMoveOutOfBook( int side )
11024 {
11025     int result = -1;
11026
11027     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11028         int index = backwardMostMove;
11029         int has_book_hit = 0;
11030
11031         if( (index % 2) != side ) {
11032             index++;
11033         }
11034
11035         while( index < forwardMostMove ) {
11036             /* Check to see if engine is in book */
11037             int depth = pvInfoList[index].depth;
11038             int score = pvInfoList[index].score;
11039             int in_book = 0;
11040
11041             if( depth <= 2 ) {
11042                 in_book = 1;
11043             }
11044             else if( score == 0 && depth == 63 ) {
11045                 in_book = 1; /* Zappa */
11046             }
11047             else if( score == 2 && depth == 99 ) {
11048                 in_book = 1; /* Abrok */
11049             }
11050
11051             has_book_hit += in_book;
11052
11053             if( ! in_book ) {
11054                 result = index;
11055
11056                 break;
11057             }
11058
11059             index += 2;
11060         }
11061     }
11062
11063     return result;
11064 }
11065
11066 /* [AS] */
11067 void GetOutOfBookInfo( char * buf )
11068 {
11069     int oob[2];
11070     int i;
11071     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11072
11073     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11074     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11075
11076     *buf = '\0';
11077
11078     if( oob[0] >= 0 || oob[1] >= 0 ) {
11079         for( i=0; i<2; i++ ) {
11080             int idx = oob[i];
11081
11082             if( idx >= 0 ) {
11083                 if( i > 0 && oob[0] >= 0 ) {
11084                     strcat( buf, "   " );
11085                 }
11086
11087                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11088                 sprintf( buf+strlen(buf), "%s%.2f",
11089                     pvInfoList[idx].score >= 0 ? "+" : "",
11090                     pvInfoList[idx].score / 100.0 );
11091             }
11092         }
11093     }
11094 }
11095
11096 /* Save game in PGN style and close the file */
11097 int
11098 SaveGamePGN(f)
11099      FILE *f;
11100 {
11101     int i, offset, linelen, newblock;
11102     time_t tm;
11103 //    char *movetext;
11104     char numtext[32];
11105     int movelen, numlen, blank;
11106     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11107
11108     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11109
11110     tm = time((time_t *) NULL);
11111
11112     PrintPGNTags(f, &gameInfo);
11113
11114     if (backwardMostMove > 0 || startedFromSetupPosition) {
11115         char *fen = PositionToFEN(backwardMostMove, NULL);
11116         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11117         fprintf(f, "\n{--------------\n");
11118         PrintPosition(f, backwardMostMove);
11119         fprintf(f, "--------------}\n");
11120         free(fen);
11121     }
11122     else {
11123         /* [AS] Out of book annotation */
11124         if( appData.saveOutOfBookInfo ) {
11125             char buf[64];
11126
11127             GetOutOfBookInfo( buf );
11128
11129             if( buf[0] != '\0' ) {
11130                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11131             }
11132         }
11133
11134         fprintf(f, "\n");
11135     }
11136
11137     i = backwardMostMove;
11138     linelen = 0;
11139     newblock = TRUE;
11140
11141     while (i < forwardMostMove) {
11142         /* Print comments preceding this move */
11143         if (commentList[i] != NULL) {
11144             if (linelen > 0) fprintf(f, "\n");
11145             fprintf(f, "%s", commentList[i]);
11146             linelen = 0;
11147             newblock = TRUE;
11148         }
11149
11150         /* Format move number */
11151         if ((i % 2) == 0)
11152           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11153         else
11154           if (newblock)
11155             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11156           else
11157             numtext[0] = NULLCHAR;
11158
11159         numlen = strlen(numtext);
11160         newblock = FALSE;
11161
11162         /* Print move number */
11163         blank = linelen > 0 && numlen > 0;
11164         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11165             fprintf(f, "\n");
11166             linelen = 0;
11167             blank = 0;
11168         }
11169         if (blank) {
11170             fprintf(f, " ");
11171             linelen++;
11172         }
11173         fprintf(f, "%s", numtext);
11174         linelen += numlen;
11175
11176         /* Get move */
11177         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11178         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11179
11180         /* Print move */
11181         blank = linelen > 0 && movelen > 0;
11182         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11183             fprintf(f, "\n");
11184             linelen = 0;
11185             blank = 0;
11186         }
11187         if (blank) {
11188             fprintf(f, " ");
11189             linelen++;
11190         }
11191         fprintf(f, "%s", move_buffer);
11192         linelen += movelen;
11193
11194         /* [AS] Add PV info if present */
11195         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11196             /* [HGM] add time */
11197             char buf[MSG_SIZ]; int seconds;
11198
11199             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11200
11201             if( seconds <= 0)
11202               buf[0] = 0;
11203             else
11204               if( seconds < 30 )
11205                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11206               else
11207                 {
11208                   seconds = (seconds + 4)/10; // round to full seconds
11209                   if( seconds < 60 )
11210                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11211                   else
11212                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11213                 }
11214
11215             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11216                       pvInfoList[i].score >= 0 ? "+" : "",
11217                       pvInfoList[i].score / 100.0,
11218                       pvInfoList[i].depth,
11219                       buf );
11220
11221             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11222
11223             /* Print score/depth */
11224             blank = linelen > 0 && movelen > 0;
11225             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11226                 fprintf(f, "\n");
11227                 linelen = 0;
11228                 blank = 0;
11229             }
11230             if (blank) {
11231                 fprintf(f, " ");
11232                 linelen++;
11233             }
11234             fprintf(f, "%s", move_buffer);
11235             linelen += movelen;
11236         }
11237
11238         i++;
11239     }
11240
11241     /* Start a new line */
11242     if (linelen > 0) fprintf(f, "\n");
11243
11244     /* Print comments after last move */
11245     if (commentList[i] != NULL) {
11246         fprintf(f, "%s\n", commentList[i]);
11247     }
11248
11249     /* Print result */
11250     if (gameInfo.resultDetails != NULL &&
11251         gameInfo.resultDetails[0] != NULLCHAR) {
11252         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11253                 PGNResult(gameInfo.result));
11254     } else {
11255         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11256     }
11257
11258     fclose(f);
11259     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11260     return TRUE;
11261 }
11262
11263 /* Save game in old style and close the file */
11264 int
11265 SaveGameOldStyle(f)
11266      FILE *f;
11267 {
11268     int i, offset;
11269     time_t tm;
11270
11271     tm = time((time_t *) NULL);
11272
11273     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11274     PrintOpponents(f);
11275
11276     if (backwardMostMove > 0 || startedFromSetupPosition) {
11277         fprintf(f, "\n[--------------\n");
11278         PrintPosition(f, backwardMostMove);
11279         fprintf(f, "--------------]\n");
11280     } else {
11281         fprintf(f, "\n");
11282     }
11283
11284     i = backwardMostMove;
11285     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11286
11287     while (i < forwardMostMove) {
11288         if (commentList[i] != NULL) {
11289             fprintf(f, "[%s]\n", commentList[i]);
11290         }
11291
11292         if ((i % 2) == 1) {
11293             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11294             i++;
11295         } else {
11296             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11297             i++;
11298             if (commentList[i] != NULL) {
11299                 fprintf(f, "\n");
11300                 continue;
11301             }
11302             if (i >= forwardMostMove) {
11303                 fprintf(f, "\n");
11304                 break;
11305             }
11306             fprintf(f, "%s\n", parseList[i]);
11307             i++;
11308         }
11309     }
11310
11311     if (commentList[i] != NULL) {
11312         fprintf(f, "[%s]\n", commentList[i]);
11313     }
11314
11315     /* This isn't really the old style, but it's close enough */
11316     if (gameInfo.resultDetails != NULL &&
11317         gameInfo.resultDetails[0] != NULLCHAR) {
11318         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11319                 gameInfo.resultDetails);
11320     } else {
11321         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11322     }
11323
11324     fclose(f);
11325     return TRUE;
11326 }
11327
11328 /* Save the current game to open file f and close the file */
11329 int
11330 SaveGame(f, dummy, dummy2)
11331      FILE *f;
11332      int dummy;
11333      char *dummy2;
11334 {
11335     if (gameMode == EditPosition) EditPositionDone(TRUE);
11336     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11337     if (appData.oldSaveStyle)
11338       return SaveGameOldStyle(f);
11339     else
11340       return SaveGamePGN(f);
11341 }
11342
11343 /* Save the current position to the given file */
11344 int
11345 SavePositionToFile(filename)
11346      char *filename;
11347 {
11348     FILE *f;
11349     char buf[MSG_SIZ];
11350
11351     if (strcmp(filename, "-") == 0) {
11352         return SavePosition(stdout, 0, NULL);
11353     } else {
11354         f = fopen(filename, "a");
11355         if (f == NULL) {
11356             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11357             DisplayError(buf, errno);
11358             return FALSE;
11359         } else {
11360             SavePosition(f, 0, NULL);
11361             return TRUE;
11362         }
11363     }
11364 }
11365
11366 /* Save the current position to the given open file and close the file */
11367 int
11368 SavePosition(f, dummy, dummy2)
11369      FILE *f;
11370      int dummy;
11371      char *dummy2;
11372 {
11373     time_t tm;
11374     char *fen;
11375
11376     if (gameMode == EditPosition) EditPositionDone(TRUE);
11377     if (appData.oldSaveStyle) {
11378         tm = time((time_t *) NULL);
11379
11380         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11381         PrintOpponents(f);
11382         fprintf(f, "[--------------\n");
11383         PrintPosition(f, currentMove);
11384         fprintf(f, "--------------]\n");
11385     } else {
11386         fen = PositionToFEN(currentMove, NULL);
11387         fprintf(f, "%s\n", fen);
11388         free(fen);
11389     }
11390     fclose(f);
11391     return TRUE;
11392 }
11393
11394 void
11395 ReloadCmailMsgEvent(unregister)
11396      int unregister;
11397 {
11398 #if !WIN32
11399     static char *inFilename = NULL;
11400     static char *outFilename;
11401     int i;
11402     struct stat inbuf, outbuf;
11403     int status;
11404
11405     /* Any registered moves are unregistered if unregister is set, */
11406     /* i.e. invoked by the signal handler */
11407     if (unregister) {
11408         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11409             cmailMoveRegistered[i] = FALSE;
11410             if (cmailCommentList[i] != NULL) {
11411                 free(cmailCommentList[i]);
11412                 cmailCommentList[i] = NULL;
11413             }
11414         }
11415         nCmailMovesRegistered = 0;
11416     }
11417
11418     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11419         cmailResult[i] = CMAIL_NOT_RESULT;
11420     }
11421     nCmailResults = 0;
11422
11423     if (inFilename == NULL) {
11424         /* Because the filenames are static they only get malloced once  */
11425         /* and they never get freed                                      */
11426         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11427         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11428
11429         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11430         sprintf(outFilename, "%s.out", appData.cmailGameName);
11431     }
11432
11433     status = stat(outFilename, &outbuf);
11434     if (status < 0) {
11435         cmailMailedMove = FALSE;
11436     } else {
11437         status = stat(inFilename, &inbuf);
11438         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11439     }
11440
11441     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11442        counts the games, notes how each one terminated, etc.
11443
11444        It would be nice to remove this kludge and instead gather all
11445        the information while building the game list.  (And to keep it
11446        in the game list nodes instead of having a bunch of fixed-size
11447        parallel arrays.)  Note this will require getting each game's
11448        termination from the PGN tags, as the game list builder does
11449        not process the game moves.  --mann
11450        */
11451     cmailMsgLoaded = TRUE;
11452     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11453
11454     /* Load first game in the file or popup game menu */
11455     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11456
11457 #endif /* !WIN32 */
11458     return;
11459 }
11460
11461 int
11462 RegisterMove()
11463 {
11464     FILE *f;
11465     char string[MSG_SIZ];
11466
11467     if (   cmailMailedMove
11468         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11469         return TRUE;            /* Allow free viewing  */
11470     }
11471
11472     /* Unregister move to ensure that we don't leave RegisterMove        */
11473     /* with the move registered when the conditions for registering no   */
11474     /* longer hold                                                       */
11475     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11476         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11477         nCmailMovesRegistered --;
11478
11479         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11480           {
11481               free(cmailCommentList[lastLoadGameNumber - 1]);
11482               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11483           }
11484     }
11485
11486     if (cmailOldMove == -1) {
11487         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11488         return FALSE;
11489     }
11490
11491     if (currentMove > cmailOldMove + 1) {
11492         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11493         return FALSE;
11494     }
11495
11496     if (currentMove < cmailOldMove) {
11497         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11498         return FALSE;
11499     }
11500
11501     if (forwardMostMove > currentMove) {
11502         /* Silently truncate extra moves */
11503         TruncateGame();
11504     }
11505
11506     if (   (currentMove == cmailOldMove + 1)
11507         || (   (currentMove == cmailOldMove)
11508             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11509                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11510         if (gameInfo.result != GameUnfinished) {
11511             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11512         }
11513
11514         if (commentList[currentMove] != NULL) {
11515             cmailCommentList[lastLoadGameNumber - 1]
11516               = StrSave(commentList[currentMove]);
11517         }
11518         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11519
11520         if (appData.debugMode)
11521           fprintf(debugFP, "Saving %s for game %d\n",
11522                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11523
11524         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11525
11526         f = fopen(string, "w");
11527         if (appData.oldSaveStyle) {
11528             SaveGameOldStyle(f); /* also closes the file */
11529
11530             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11531             f = fopen(string, "w");
11532             SavePosition(f, 0, NULL); /* also closes the file */
11533         } else {
11534             fprintf(f, "{--------------\n");
11535             PrintPosition(f, currentMove);
11536             fprintf(f, "--------------}\n\n");
11537
11538             SaveGame(f, 0, NULL); /* also closes the file*/
11539         }
11540
11541         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11542         nCmailMovesRegistered ++;
11543     } else if (nCmailGames == 1) {
11544         DisplayError(_("You have not made a move yet"), 0);
11545         return FALSE;
11546     }
11547
11548     return TRUE;
11549 }
11550
11551 void
11552 MailMoveEvent()
11553 {
11554 #if !WIN32
11555     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11556     FILE *commandOutput;
11557     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11558     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11559     int nBuffers;
11560     int i;
11561     int archived;
11562     char *arcDir;
11563
11564     if (! cmailMsgLoaded) {
11565         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11566         return;
11567     }
11568
11569     if (nCmailGames == nCmailResults) {
11570         DisplayError(_("No unfinished games"), 0);
11571         return;
11572     }
11573
11574 #if CMAIL_PROHIBIT_REMAIL
11575     if (cmailMailedMove) {
11576       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);
11577         DisplayError(msg, 0);
11578         return;
11579     }
11580 #endif
11581
11582     if (! (cmailMailedMove || RegisterMove())) return;
11583
11584     if (   cmailMailedMove
11585         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11586       snprintf(string, MSG_SIZ, partCommandString,
11587                appData.debugMode ? " -v" : "", appData.cmailGameName);
11588         commandOutput = popen(string, "r");
11589
11590         if (commandOutput == NULL) {
11591             DisplayError(_("Failed to invoke cmail"), 0);
11592         } else {
11593             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11594                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11595             }
11596             if (nBuffers > 1) {
11597                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11598                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11599                 nBytes = MSG_SIZ - 1;
11600             } else {
11601                 (void) memcpy(msg, buffer, nBytes);
11602             }
11603             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11604
11605             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11606                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11607
11608                 archived = TRUE;
11609                 for (i = 0; i < nCmailGames; i ++) {
11610                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11611                         archived = FALSE;
11612                     }
11613                 }
11614                 if (   archived
11615                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11616                         != NULL)) {
11617                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11618                            arcDir,
11619                            appData.cmailGameName,
11620                            gameInfo.date);
11621                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11622                     cmailMsgLoaded = FALSE;
11623                 }
11624             }
11625
11626             DisplayInformation(msg);
11627             pclose(commandOutput);
11628         }
11629     } else {
11630         if ((*cmailMsg) != '\0') {
11631             DisplayInformation(cmailMsg);
11632         }
11633     }
11634
11635     return;
11636 #endif /* !WIN32 */
11637 }
11638
11639 char *
11640 CmailMsg()
11641 {
11642 #if WIN32
11643     return NULL;
11644 #else
11645     int  prependComma = 0;
11646     char number[5];
11647     char string[MSG_SIZ];       /* Space for game-list */
11648     int  i;
11649
11650     if (!cmailMsgLoaded) return "";
11651
11652     if (cmailMailedMove) {
11653       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11654     } else {
11655         /* Create a list of games left */
11656       snprintf(string, MSG_SIZ, "[");
11657         for (i = 0; i < nCmailGames; i ++) {
11658             if (! (   cmailMoveRegistered[i]
11659                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11660                 if (prependComma) {
11661                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11662                 } else {
11663                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11664                     prependComma = 1;
11665                 }
11666
11667                 strcat(string, number);
11668             }
11669         }
11670         strcat(string, "]");
11671
11672         if (nCmailMovesRegistered + nCmailResults == 0) {
11673             switch (nCmailGames) {
11674               case 1:
11675                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11676                 break;
11677
11678               case 2:
11679                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11680                 break;
11681
11682               default:
11683                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11684                          nCmailGames);
11685                 break;
11686             }
11687         } else {
11688             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11689               case 1:
11690                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11691                          string);
11692                 break;
11693
11694               case 0:
11695                 if (nCmailResults == nCmailGames) {
11696                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11697                 } else {
11698                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11699                 }
11700                 break;
11701
11702               default:
11703                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11704                          string);
11705             }
11706         }
11707     }
11708     return cmailMsg;
11709 #endif /* WIN32 */
11710 }
11711
11712 void
11713 ResetGameEvent()
11714 {
11715     if (gameMode == Training)
11716       SetTrainingModeOff();
11717
11718     Reset(TRUE, TRUE);
11719     cmailMsgLoaded = FALSE;
11720     if (appData.icsActive) {
11721       SendToICS(ics_prefix);
11722       SendToICS("refresh\n");
11723     }
11724 }
11725
11726 void
11727 ExitEvent(status)
11728      int status;
11729 {
11730     exiting++;
11731     if (exiting > 2) {
11732       /* Give up on clean exit */
11733       exit(status);
11734     }
11735     if (exiting > 1) {
11736       /* Keep trying for clean exit */
11737       return;
11738     }
11739
11740     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11741
11742     if (telnetISR != NULL) {
11743       RemoveInputSource(telnetISR);
11744     }
11745     if (icsPR != NoProc) {
11746       DestroyChildProcess(icsPR, TRUE);
11747     }
11748
11749     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11750     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11751
11752     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11753     /* make sure this other one finishes before killing it!                  */
11754     if(endingGame) { int count = 0;
11755         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11756         while(endingGame && count++ < 10) DoSleep(1);
11757         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11758     }
11759
11760     /* Kill off chess programs */
11761     if (first.pr != NoProc) {
11762         ExitAnalyzeMode();
11763
11764         DoSleep( appData.delayBeforeQuit );
11765         SendToProgram("quit\n", &first);
11766         DoSleep( appData.delayAfterQuit );
11767         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11768     }
11769     if (second.pr != NoProc) {
11770         DoSleep( appData.delayBeforeQuit );
11771         SendToProgram("quit\n", &second);
11772         DoSleep( appData.delayAfterQuit );
11773         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11774     }
11775     if (first.isr != NULL) {
11776         RemoveInputSource(first.isr);
11777     }
11778     if (second.isr != NULL) {
11779         RemoveInputSource(second.isr);
11780     }
11781
11782     ShutDownFrontEnd();
11783     exit(status);
11784 }
11785
11786 void
11787 PauseEvent()
11788 {
11789     if (appData.debugMode)
11790         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11791     if (pausing) {
11792         pausing = FALSE;
11793         ModeHighlight();
11794         if (gameMode == MachinePlaysWhite ||
11795             gameMode == MachinePlaysBlack) {
11796             StartClocks();
11797         } else {
11798             DisplayBothClocks();
11799         }
11800         if (gameMode == PlayFromGameFile) {
11801             if (appData.timeDelay >= 0)
11802                 AutoPlayGameLoop();
11803         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11804             Reset(FALSE, TRUE);
11805             SendToICS(ics_prefix);
11806             SendToICS("refresh\n");
11807         } else if (currentMove < forwardMostMove) {
11808             ForwardInner(forwardMostMove);
11809         }
11810         pauseExamInvalid = FALSE;
11811     } else {
11812         switch (gameMode) {
11813           default:
11814             return;
11815           case IcsExamining:
11816             pauseExamForwardMostMove = forwardMostMove;
11817             pauseExamInvalid = FALSE;
11818             /* fall through */
11819           case IcsObserving:
11820           case IcsPlayingWhite:
11821           case IcsPlayingBlack:
11822             pausing = TRUE;
11823             ModeHighlight();
11824             return;
11825           case PlayFromGameFile:
11826             (void) StopLoadGameTimer();
11827             pausing = TRUE;
11828             ModeHighlight();
11829             break;
11830           case BeginningOfGame:
11831             if (appData.icsActive) return;
11832             /* else fall through */
11833           case MachinePlaysWhite:
11834           case MachinePlaysBlack:
11835           case TwoMachinesPlay:
11836             if (forwardMostMove == 0)
11837               return;           /* don't pause if no one has moved */
11838             if ((gameMode == MachinePlaysWhite &&
11839                  !WhiteOnMove(forwardMostMove)) ||
11840                 (gameMode == MachinePlaysBlack &&
11841                  WhiteOnMove(forwardMostMove))) {
11842                 StopClocks();
11843             }
11844             pausing = TRUE;
11845             ModeHighlight();
11846             break;
11847         }
11848     }
11849 }
11850
11851 void
11852 EditCommentEvent()
11853 {
11854     char title[MSG_SIZ];
11855
11856     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11857       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11858     } else {
11859       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11860                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11861                parseList[currentMove - 1]);
11862     }
11863
11864     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11865 }
11866
11867
11868 void
11869 EditTagsEvent()
11870 {
11871     char *tags = PGNTags(&gameInfo);
11872     EditTagsPopUp(tags, NULL);
11873     free(tags);
11874 }
11875
11876 void
11877 AnalyzeModeEvent()
11878 {
11879     if (appData.noChessProgram || gameMode == AnalyzeMode)
11880       return;
11881
11882     if (gameMode != AnalyzeFile) {
11883         if (!appData.icsEngineAnalyze) {
11884                EditGameEvent();
11885                if (gameMode != EditGame) return;
11886         }
11887         ResurrectChessProgram();
11888         SendToProgram("analyze\n", &first);
11889         first.analyzing = TRUE;
11890         /*first.maybeThinking = TRUE;*/
11891         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11892         EngineOutputPopUp();
11893     }
11894     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11895     pausing = FALSE;
11896     ModeHighlight();
11897     SetGameInfo();
11898
11899     StartAnalysisClock();
11900     GetTimeMark(&lastNodeCountTime);
11901     lastNodeCount = 0;
11902 }
11903
11904 void
11905 AnalyzeFileEvent()
11906 {
11907     if (appData.noChessProgram || gameMode == AnalyzeFile)
11908       return;
11909
11910     if (gameMode != AnalyzeMode) {
11911         EditGameEvent();
11912         if (gameMode != EditGame) return;
11913         ResurrectChessProgram();
11914         SendToProgram("analyze\n", &first);
11915         first.analyzing = TRUE;
11916         /*first.maybeThinking = TRUE;*/
11917         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11918         EngineOutputPopUp();
11919     }
11920     gameMode = AnalyzeFile;
11921     pausing = FALSE;
11922     ModeHighlight();
11923     SetGameInfo();
11924
11925     StartAnalysisClock();
11926     GetTimeMark(&lastNodeCountTime);
11927     lastNodeCount = 0;
11928 }
11929
11930 void
11931 MachineWhiteEvent()
11932 {
11933     char buf[MSG_SIZ];
11934     char *bookHit = NULL;
11935
11936     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11937       return;
11938
11939
11940     if (gameMode == PlayFromGameFile ||
11941         gameMode == TwoMachinesPlay  ||
11942         gameMode == Training         ||
11943         gameMode == AnalyzeMode      ||
11944         gameMode == EndOfGame)
11945         EditGameEvent();
11946
11947     if (gameMode == EditPosition)
11948         EditPositionDone(TRUE);
11949
11950     if (!WhiteOnMove(currentMove)) {
11951         DisplayError(_("It is not White's turn"), 0);
11952         return;
11953     }
11954
11955     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11956       ExitAnalyzeMode();
11957
11958     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11959         gameMode == AnalyzeFile)
11960         TruncateGame();
11961
11962     ResurrectChessProgram();    /* in case it isn't running */
11963     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11964         gameMode = MachinePlaysWhite;
11965         ResetClocks();
11966     } else
11967     gameMode = MachinePlaysWhite;
11968     pausing = FALSE;
11969     ModeHighlight();
11970     SetGameInfo();
11971     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11972     DisplayTitle(buf);
11973     if (first.sendName) {
11974       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11975       SendToProgram(buf, &first);
11976     }
11977     if (first.sendTime) {
11978       if (first.useColors) {
11979         SendToProgram("black\n", &first); /*gnu kludge*/
11980       }
11981       SendTimeRemaining(&first, TRUE);
11982     }
11983     if (first.useColors) {
11984       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11985     }
11986     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11987     SetMachineThinkingEnables();
11988     first.maybeThinking = TRUE;
11989     StartClocks();
11990     firstMove = FALSE;
11991
11992     if (appData.autoFlipView && !flipView) {
11993       flipView = !flipView;
11994       DrawPosition(FALSE, NULL);
11995       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11996     }
11997
11998     if(bookHit) { // [HGM] book: simulate book reply
11999         static char bookMove[MSG_SIZ]; // a bit generous?
12000
12001         programStats.nodes = programStats.depth = programStats.time =
12002         programStats.score = programStats.got_only_move = 0;
12003         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12004
12005         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12006         strcat(bookMove, bookHit);
12007         HandleMachineMove(bookMove, &first);
12008     }
12009 }
12010
12011 void
12012 MachineBlackEvent()
12013 {
12014   char buf[MSG_SIZ];
12015   char *bookHit = NULL;
12016
12017     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12018         return;
12019
12020
12021     if (gameMode == PlayFromGameFile ||
12022         gameMode == TwoMachinesPlay  ||
12023         gameMode == Training         ||
12024         gameMode == AnalyzeMode      ||
12025         gameMode == EndOfGame)
12026         EditGameEvent();
12027
12028     if (gameMode == EditPosition)
12029         EditPositionDone(TRUE);
12030
12031     if (WhiteOnMove(currentMove)) {
12032         DisplayError(_("It is not Black's turn"), 0);
12033         return;
12034     }
12035
12036     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12037       ExitAnalyzeMode();
12038
12039     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12040         gameMode == AnalyzeFile)
12041         TruncateGame();
12042
12043     ResurrectChessProgram();    /* in case it isn't running */
12044     gameMode = MachinePlaysBlack;
12045     pausing = FALSE;
12046     ModeHighlight();
12047     SetGameInfo();
12048     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12049     DisplayTitle(buf);
12050     if (first.sendName) {
12051       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12052       SendToProgram(buf, &first);
12053     }
12054     if (first.sendTime) {
12055       if (first.useColors) {
12056         SendToProgram("white\n", &first); /*gnu kludge*/
12057       }
12058       SendTimeRemaining(&first, FALSE);
12059     }
12060     if (first.useColors) {
12061       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12062     }
12063     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12064     SetMachineThinkingEnables();
12065     first.maybeThinking = TRUE;
12066     StartClocks();
12067
12068     if (appData.autoFlipView && flipView) {
12069       flipView = !flipView;
12070       DrawPosition(FALSE, NULL);
12071       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12072     }
12073     if(bookHit) { // [HGM] book: simulate book reply
12074         static char bookMove[MSG_SIZ]; // a bit generous?
12075
12076         programStats.nodes = programStats.depth = programStats.time =
12077         programStats.score = programStats.got_only_move = 0;
12078         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12079
12080         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12081         strcat(bookMove, bookHit);
12082         HandleMachineMove(bookMove, &first);
12083     }
12084 }
12085
12086
12087 void
12088 DisplayTwoMachinesTitle()
12089 {
12090     char buf[MSG_SIZ];
12091     if (appData.matchGames > 0) {
12092         if (first.twoMachinesColor[0] == 'w') {
12093           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12094                    gameInfo.white, gameInfo.black,
12095                    first.matchWins, second.matchWins,
12096                    matchGame - 1 - (first.matchWins + second.matchWins));
12097         } else {
12098           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12099                    gameInfo.white, gameInfo.black,
12100                    second.matchWins, first.matchWins,
12101                    matchGame - 1 - (first.matchWins + second.matchWins));
12102         }
12103     } else {
12104       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12105     }
12106     DisplayTitle(buf);
12107 }
12108
12109 void
12110 SettingsMenuIfReady()
12111 {
12112   if (second.lastPing != second.lastPong) {
12113     DisplayMessage("", _("Waiting for second chess program"));
12114     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12115     return;
12116   }
12117   ThawUI();
12118   DisplayMessage("", "");
12119   SettingsPopUp(&second);
12120 }
12121
12122 int
12123 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12124 {
12125     char buf[MSG_SIZ];
12126     if (cps->pr == NULL) {
12127         StartChessProgram(cps);
12128         if (cps->protocolVersion == 1) {
12129           retry();
12130         } else {
12131           /* kludge: allow timeout for initial "feature" command */
12132           FreezeUI();
12133           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12134           DisplayMessage("", buf);
12135           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12136         }
12137         return 1;
12138     }
12139     return 0;
12140 }
12141
12142 void
12143 TwoMachinesEvent P((void))
12144 {
12145     int i;
12146     char buf[MSG_SIZ];
12147     ChessProgramState *onmove;
12148     char *bookHit = NULL;
12149     static int stalling = 0;
12150
12151     if (appData.noChessProgram) return;
12152
12153     switch (gameMode) {
12154       case TwoMachinesPlay:
12155         return;
12156       case MachinePlaysWhite:
12157       case MachinePlaysBlack:
12158         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12159             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12160             return;
12161         }
12162         /* fall through */
12163       case BeginningOfGame:
12164       case PlayFromGameFile:
12165       case EndOfGame:
12166         EditGameEvent();
12167         if (gameMode != EditGame) return;
12168         break;
12169       case EditPosition:
12170         EditPositionDone(TRUE);
12171         break;
12172       case AnalyzeMode:
12173       case AnalyzeFile:
12174         ExitAnalyzeMode();
12175         break;
12176       case EditGame:
12177       default:
12178         break;
12179     }
12180
12181 //    forwardMostMove = currentMove;
12182     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12183     ResurrectChessProgram();    /* in case first program isn't running */
12184
12185     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12186     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12187       DisplayMessage("", _("Waiting for first chess program"));
12188       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12189       return;
12190     }
12191     if(!stalling) {
12192       InitChessProgram(&second, FALSE);
12193       SendToProgram("force\n", &second);
12194     }
12195     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12196       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12197       stalling = 1;
12198       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12199       return;
12200     }
12201     stalling = 0;
12202     DisplayMessage("", "");
12203     if (startedFromSetupPosition) {
12204         SendBoard(&second, backwardMostMove);
12205     if (appData.debugMode) {
12206         fprintf(debugFP, "Two Machines\n");
12207     }
12208     }
12209     for (i = backwardMostMove; i < forwardMostMove; i++) {
12210         SendMoveToProgram(i, &second);
12211     }
12212
12213     gameMode = TwoMachinesPlay;
12214     pausing = FALSE;
12215     ModeHighlight();
12216     SetGameInfo();
12217     DisplayTwoMachinesTitle();
12218     firstMove = TRUE;
12219     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12220         onmove = &first;
12221     } else {
12222         onmove = &second;
12223     }
12224
12225     SendToProgram(first.computerString, &first);
12226     if (first.sendName) {
12227       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12228       SendToProgram(buf, &first);
12229     }
12230     SendToProgram(second.computerString, &second);
12231     if (second.sendName) {
12232       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12233       SendToProgram(buf, &second);
12234     }
12235
12236     ResetClocks();
12237     if (!first.sendTime || !second.sendTime) {
12238         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12239         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12240     }
12241     if (onmove->sendTime) {
12242       if (onmove->useColors) {
12243         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12244       }
12245       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12246     }
12247     if (onmove->useColors) {
12248       SendToProgram(onmove->twoMachinesColor, onmove);
12249     }
12250     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12251 //    SendToProgram("go\n", onmove);
12252     onmove->maybeThinking = TRUE;
12253     SetMachineThinkingEnables();
12254
12255     StartClocks();
12256
12257     if(bookHit) { // [HGM] book: simulate book reply
12258         static char bookMove[MSG_SIZ]; // a bit generous?
12259
12260         programStats.nodes = programStats.depth = programStats.time =
12261         programStats.score = programStats.got_only_move = 0;
12262         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12263
12264         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12265         strcat(bookMove, bookHit);
12266         savedMessage = bookMove; // args for deferred call
12267         savedState = onmove;
12268         ScheduleDelayedEvent(DeferredBookMove, 1);
12269     }
12270 }
12271
12272 void
12273 TrainingEvent()
12274 {
12275     if (gameMode == Training) {
12276       SetTrainingModeOff();
12277       gameMode = PlayFromGameFile;
12278       DisplayMessage("", _("Training mode off"));
12279     } else {
12280       gameMode = Training;
12281       animateTraining = appData.animate;
12282
12283       /* make sure we are not already at the end of the game */
12284       if (currentMove < forwardMostMove) {
12285         SetTrainingModeOn();
12286         DisplayMessage("", _("Training mode on"));
12287       } else {
12288         gameMode = PlayFromGameFile;
12289         DisplayError(_("Already at end of game"), 0);
12290       }
12291     }
12292     ModeHighlight();
12293 }
12294
12295 void
12296 IcsClientEvent()
12297 {
12298     if (!appData.icsActive) return;
12299     switch (gameMode) {
12300       case IcsPlayingWhite:
12301       case IcsPlayingBlack:
12302       case IcsObserving:
12303       case IcsIdle:
12304       case BeginningOfGame:
12305       case IcsExamining:
12306         return;
12307
12308       case EditGame:
12309         break;
12310
12311       case EditPosition:
12312         EditPositionDone(TRUE);
12313         break;
12314
12315       case AnalyzeMode:
12316       case AnalyzeFile:
12317         ExitAnalyzeMode();
12318         break;
12319
12320       default:
12321         EditGameEvent();
12322         break;
12323     }
12324
12325     gameMode = IcsIdle;
12326     ModeHighlight();
12327     return;
12328 }
12329
12330
12331 void
12332 EditGameEvent()
12333 {
12334     int i;
12335
12336     switch (gameMode) {
12337       case Training:
12338         SetTrainingModeOff();
12339         break;
12340       case MachinePlaysWhite:
12341       case MachinePlaysBlack:
12342       case BeginningOfGame:
12343         SendToProgram("force\n", &first);
12344         SetUserThinkingEnables();
12345         break;
12346       case PlayFromGameFile:
12347         (void) StopLoadGameTimer();
12348         if (gameFileFP != NULL) {
12349             gameFileFP = NULL;
12350         }
12351         break;
12352       case EditPosition:
12353         EditPositionDone(TRUE);
12354         break;
12355       case AnalyzeMode:
12356       case AnalyzeFile:
12357         ExitAnalyzeMode();
12358         SendToProgram("force\n", &first);
12359         break;
12360       case TwoMachinesPlay:
12361         GameEnds(EndOfFile, NULL, GE_PLAYER);
12362         ResurrectChessProgram();
12363         SetUserThinkingEnables();
12364         break;
12365       case EndOfGame:
12366         ResurrectChessProgram();
12367         break;
12368       case IcsPlayingBlack:
12369       case IcsPlayingWhite:
12370         DisplayError(_("Warning: You are still playing a game"), 0);
12371         break;
12372       case IcsObserving:
12373         DisplayError(_("Warning: You are still observing a game"), 0);
12374         break;
12375       case IcsExamining:
12376         DisplayError(_("Warning: You are still examining a game"), 0);
12377         break;
12378       case IcsIdle:
12379         break;
12380       case EditGame:
12381       default:
12382         return;
12383     }
12384
12385     pausing = FALSE;
12386     StopClocks();
12387     first.offeredDraw = second.offeredDraw = 0;
12388
12389     if (gameMode == PlayFromGameFile) {
12390         whiteTimeRemaining = timeRemaining[0][currentMove];
12391         blackTimeRemaining = timeRemaining[1][currentMove];
12392         DisplayTitle("");
12393     }
12394
12395     if (gameMode == MachinePlaysWhite ||
12396         gameMode == MachinePlaysBlack ||
12397         gameMode == TwoMachinesPlay ||
12398         gameMode == EndOfGame) {
12399         i = forwardMostMove;
12400         while (i > currentMove) {
12401             SendToProgram("undo\n", &first);
12402             i--;
12403         }
12404         whiteTimeRemaining = timeRemaining[0][currentMove];
12405         blackTimeRemaining = timeRemaining[1][currentMove];
12406         DisplayBothClocks();
12407         if (whiteFlag || blackFlag) {
12408             whiteFlag = blackFlag = 0;
12409         }
12410         DisplayTitle("");
12411     }
12412
12413     gameMode = EditGame;
12414     ModeHighlight();
12415     SetGameInfo();
12416 }
12417
12418
12419 void
12420 EditPositionEvent()
12421 {
12422     if (gameMode == EditPosition) {
12423         EditGameEvent();
12424         return;
12425     }
12426
12427     EditGameEvent();
12428     if (gameMode != EditGame) return;
12429
12430     gameMode = EditPosition;
12431     ModeHighlight();
12432     SetGameInfo();
12433     if (currentMove > 0)
12434       CopyBoard(boards[0], boards[currentMove]);
12435
12436     blackPlaysFirst = !WhiteOnMove(currentMove);
12437     ResetClocks();
12438     currentMove = forwardMostMove = backwardMostMove = 0;
12439     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12440     DisplayMove(-1);
12441 }
12442
12443 void
12444 ExitAnalyzeMode()
12445 {
12446     /* [DM] icsEngineAnalyze - possible call from other functions */
12447     if (appData.icsEngineAnalyze) {
12448         appData.icsEngineAnalyze = FALSE;
12449
12450         DisplayMessage("",_("Close ICS engine analyze..."));
12451     }
12452     if (first.analysisSupport && first.analyzing) {
12453       SendToProgram("exit\n", &first);
12454       first.analyzing = FALSE;
12455     }
12456     thinkOutput[0] = NULLCHAR;
12457 }
12458
12459 void
12460 EditPositionDone(Boolean fakeRights)
12461 {
12462     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12463
12464     startedFromSetupPosition = TRUE;
12465     InitChessProgram(&first, FALSE);
12466     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12467       boards[0][EP_STATUS] = EP_NONE;
12468       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12469     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12470         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12471         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12472       } else boards[0][CASTLING][2] = NoRights;
12473     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12474         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12475         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12476       } else boards[0][CASTLING][5] = NoRights;
12477     }
12478     SendToProgram("force\n", &first);
12479     if (blackPlaysFirst) {
12480         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12481         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12482         currentMove = forwardMostMove = backwardMostMove = 1;
12483         CopyBoard(boards[1], boards[0]);
12484     } else {
12485         currentMove = forwardMostMove = backwardMostMove = 0;
12486     }
12487     SendBoard(&first, forwardMostMove);
12488     if (appData.debugMode) {
12489         fprintf(debugFP, "EditPosDone\n");
12490     }
12491     DisplayTitle("");
12492     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12493     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12494     gameMode = EditGame;
12495     ModeHighlight();
12496     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12497     ClearHighlights(); /* [AS] */
12498 }
12499
12500 /* Pause for `ms' milliseconds */
12501 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12502 void
12503 TimeDelay(ms)
12504      long ms;
12505 {
12506     TimeMark m1, m2;
12507
12508     GetTimeMark(&m1);
12509     do {
12510         GetTimeMark(&m2);
12511     } while (SubtractTimeMarks(&m2, &m1) < ms);
12512 }
12513
12514 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12515 void
12516 SendMultiLineToICS(buf)
12517      char *buf;
12518 {
12519     char temp[MSG_SIZ+1], *p;
12520     int len;
12521
12522     len = strlen(buf);
12523     if (len > MSG_SIZ)
12524       len = MSG_SIZ;
12525
12526     strncpy(temp, buf, len);
12527     temp[len] = 0;
12528
12529     p = temp;
12530     while (*p) {
12531         if (*p == '\n' || *p == '\r')
12532           *p = ' ';
12533         ++p;
12534     }
12535
12536     strcat(temp, "\n");
12537     SendToICS(temp);
12538     SendToPlayer(temp, strlen(temp));
12539 }
12540
12541 void
12542 SetWhiteToPlayEvent()
12543 {
12544     if (gameMode == EditPosition) {
12545         blackPlaysFirst = FALSE;
12546         DisplayBothClocks();    /* works because currentMove is 0 */
12547     } else if (gameMode == IcsExamining) {
12548         SendToICS(ics_prefix);
12549         SendToICS("tomove white\n");
12550     }
12551 }
12552
12553 void
12554 SetBlackToPlayEvent()
12555 {
12556     if (gameMode == EditPosition) {
12557         blackPlaysFirst = TRUE;
12558         currentMove = 1;        /* kludge */
12559         DisplayBothClocks();
12560         currentMove = 0;
12561     } else if (gameMode == IcsExamining) {
12562         SendToICS(ics_prefix);
12563         SendToICS("tomove black\n");
12564     }
12565 }
12566
12567 void
12568 EditPositionMenuEvent(selection, x, y)
12569      ChessSquare selection;
12570      int x, y;
12571 {
12572     char buf[MSG_SIZ];
12573     ChessSquare piece = boards[0][y][x];
12574
12575     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12576
12577     switch (selection) {
12578       case ClearBoard:
12579         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12580             SendToICS(ics_prefix);
12581             SendToICS("bsetup clear\n");
12582         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12583             SendToICS(ics_prefix);
12584             SendToICS("clearboard\n");
12585         } else {
12586             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12587                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12588                 for (y = 0; y < BOARD_HEIGHT; y++) {
12589                     if (gameMode == IcsExamining) {
12590                         if (boards[currentMove][y][x] != EmptySquare) {
12591                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12592                                     AAA + x, ONE + y);
12593                             SendToICS(buf);
12594                         }
12595                     } else {
12596                         boards[0][y][x] = p;
12597                     }
12598                 }
12599             }
12600         }
12601         if (gameMode == EditPosition) {
12602             DrawPosition(FALSE, boards[0]);
12603         }
12604         break;
12605
12606       case WhitePlay:
12607         SetWhiteToPlayEvent();
12608         break;
12609
12610       case BlackPlay:
12611         SetBlackToPlayEvent();
12612         break;
12613
12614       case EmptySquare:
12615         if (gameMode == IcsExamining) {
12616             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12617             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12618             SendToICS(buf);
12619         } else {
12620             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12621                 if(x == BOARD_LEFT-2) {
12622                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12623                     boards[0][y][1] = 0;
12624                 } else
12625                 if(x == BOARD_RGHT+1) {
12626                     if(y >= gameInfo.holdingsSize) break;
12627                     boards[0][y][BOARD_WIDTH-2] = 0;
12628                 } else break;
12629             }
12630             boards[0][y][x] = EmptySquare;
12631             DrawPosition(FALSE, boards[0]);
12632         }
12633         break;
12634
12635       case PromotePiece:
12636         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12637            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12638             selection = (ChessSquare) (PROMOTED piece);
12639         } else if(piece == EmptySquare) selection = WhiteSilver;
12640         else selection = (ChessSquare)((int)piece - 1);
12641         goto defaultlabel;
12642
12643       case DemotePiece:
12644         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12645            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12646             selection = (ChessSquare) (DEMOTED piece);
12647         } else if(piece == EmptySquare) selection = BlackSilver;
12648         else selection = (ChessSquare)((int)piece + 1);
12649         goto defaultlabel;
12650
12651       case WhiteQueen:
12652       case BlackQueen:
12653         if(gameInfo.variant == VariantShatranj ||
12654            gameInfo.variant == VariantXiangqi  ||
12655            gameInfo.variant == VariantCourier  ||
12656            gameInfo.variant == VariantMakruk     )
12657             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12658         goto defaultlabel;
12659
12660       case WhiteKing:
12661       case BlackKing:
12662         if(gameInfo.variant == VariantXiangqi)
12663             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12664         if(gameInfo.variant == VariantKnightmate)
12665             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12666       default:
12667         defaultlabel:
12668         if (gameMode == IcsExamining) {
12669             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12670             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12671                      PieceToChar(selection), AAA + x, ONE + y);
12672             SendToICS(buf);
12673         } else {
12674             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12675                 int n;
12676                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12677                     n = PieceToNumber(selection - BlackPawn);
12678                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12679                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12680                     boards[0][BOARD_HEIGHT-1-n][1]++;
12681                 } else
12682                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12683                     n = PieceToNumber(selection);
12684                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12685                     boards[0][n][BOARD_WIDTH-1] = selection;
12686                     boards[0][n][BOARD_WIDTH-2]++;
12687                 }
12688             } else
12689             boards[0][y][x] = selection;
12690             DrawPosition(TRUE, boards[0]);
12691         }
12692         break;
12693     }
12694 }
12695
12696
12697 void
12698 DropMenuEvent(selection, x, y)
12699      ChessSquare selection;
12700      int x, y;
12701 {
12702     ChessMove moveType;
12703
12704     switch (gameMode) {
12705       case IcsPlayingWhite:
12706       case MachinePlaysBlack:
12707         if (!WhiteOnMove(currentMove)) {
12708             DisplayMoveError(_("It is Black's turn"));
12709             return;
12710         }
12711         moveType = WhiteDrop;
12712         break;
12713       case IcsPlayingBlack:
12714       case MachinePlaysWhite:
12715         if (WhiteOnMove(currentMove)) {
12716             DisplayMoveError(_("It is White's turn"));
12717             return;
12718         }
12719         moveType = BlackDrop;
12720         break;
12721       case EditGame:
12722         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12723         break;
12724       default:
12725         return;
12726     }
12727
12728     if (moveType == BlackDrop && selection < BlackPawn) {
12729       selection = (ChessSquare) ((int) selection
12730                                  + (int) BlackPawn - (int) WhitePawn);
12731     }
12732     if (boards[currentMove][y][x] != EmptySquare) {
12733         DisplayMoveError(_("That square is occupied"));
12734         return;
12735     }
12736
12737     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12738 }
12739
12740 void
12741 AcceptEvent()
12742 {
12743     /* Accept a pending offer of any kind from opponent */
12744
12745     if (appData.icsActive) {
12746         SendToICS(ics_prefix);
12747         SendToICS("accept\n");
12748     } else if (cmailMsgLoaded) {
12749         if (currentMove == cmailOldMove &&
12750             commentList[cmailOldMove] != NULL &&
12751             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12752                    "Black offers a draw" : "White offers a draw")) {
12753             TruncateGame();
12754             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12755             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12756         } else {
12757             DisplayError(_("There is no pending offer on this move"), 0);
12758             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12759         }
12760     } else {
12761         /* Not used for offers from chess program */
12762     }
12763 }
12764
12765 void
12766 DeclineEvent()
12767 {
12768     /* Decline a pending offer of any kind from opponent */
12769
12770     if (appData.icsActive) {
12771         SendToICS(ics_prefix);
12772         SendToICS("decline\n");
12773     } else if (cmailMsgLoaded) {
12774         if (currentMove == cmailOldMove &&
12775             commentList[cmailOldMove] != NULL &&
12776             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12777                    "Black offers a draw" : "White offers a draw")) {
12778 #ifdef NOTDEF
12779             AppendComment(cmailOldMove, "Draw declined", TRUE);
12780             DisplayComment(cmailOldMove - 1, "Draw declined");
12781 #endif /*NOTDEF*/
12782         } else {
12783             DisplayError(_("There is no pending offer on this move"), 0);
12784         }
12785     } else {
12786         /* Not used for offers from chess program */
12787     }
12788 }
12789
12790 void
12791 RematchEvent()
12792 {
12793     /* Issue ICS rematch command */
12794     if (appData.icsActive) {
12795         SendToICS(ics_prefix);
12796         SendToICS("rematch\n");
12797     }
12798 }
12799
12800 void
12801 CallFlagEvent()
12802 {
12803     /* Call your opponent's flag (claim a win on time) */
12804     if (appData.icsActive) {
12805         SendToICS(ics_prefix);
12806         SendToICS("flag\n");
12807     } else {
12808         switch (gameMode) {
12809           default:
12810             return;
12811           case MachinePlaysWhite:
12812             if (whiteFlag) {
12813                 if (blackFlag)
12814                   GameEnds(GameIsDrawn, "Both players ran out of time",
12815                            GE_PLAYER);
12816                 else
12817                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12818             } else {
12819                 DisplayError(_("Your opponent is not out of time"), 0);
12820             }
12821             break;
12822           case MachinePlaysBlack:
12823             if (blackFlag) {
12824                 if (whiteFlag)
12825                   GameEnds(GameIsDrawn, "Both players ran out of time",
12826                            GE_PLAYER);
12827                 else
12828                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12829             } else {
12830                 DisplayError(_("Your opponent is not out of time"), 0);
12831             }
12832             break;
12833         }
12834     }
12835 }
12836
12837 void
12838 ClockClick(int which)
12839 {       // [HGM] code moved to back-end from winboard.c
12840         if(which) { // black clock
12841           if (gameMode == EditPosition || gameMode == IcsExamining) {
12842             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12843             SetBlackToPlayEvent();
12844           } else if (gameMode == EditGame || shiftKey) {
12845             AdjustClock(which, -1);
12846           } else if (gameMode == IcsPlayingWhite ||
12847                      gameMode == MachinePlaysBlack) {
12848             CallFlagEvent();
12849           }
12850         } else { // white clock
12851           if (gameMode == EditPosition || gameMode == IcsExamining) {
12852             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12853             SetWhiteToPlayEvent();
12854           } else if (gameMode == EditGame || shiftKey) {
12855             AdjustClock(which, -1);
12856           } else if (gameMode == IcsPlayingBlack ||
12857                    gameMode == MachinePlaysWhite) {
12858             CallFlagEvent();
12859           }
12860         }
12861 }
12862
12863 void
12864 DrawEvent()
12865 {
12866     /* Offer draw or accept pending draw offer from opponent */
12867
12868     if (appData.icsActive) {
12869         /* Note: tournament rules require draw offers to be
12870            made after you make your move but before you punch
12871            your clock.  Currently ICS doesn't let you do that;
12872            instead, you immediately punch your clock after making
12873            a move, but you can offer a draw at any time. */
12874
12875         SendToICS(ics_prefix);
12876         SendToICS("draw\n");
12877         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12878     } else if (cmailMsgLoaded) {
12879         if (currentMove == cmailOldMove &&
12880             commentList[cmailOldMove] != NULL &&
12881             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12882                    "Black offers a draw" : "White offers a draw")) {
12883             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12884             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12885         } else if (currentMove == cmailOldMove + 1) {
12886             char *offer = WhiteOnMove(cmailOldMove) ?
12887               "White offers a draw" : "Black offers a draw";
12888             AppendComment(currentMove, offer, TRUE);
12889             DisplayComment(currentMove - 1, offer);
12890             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12891         } else {
12892             DisplayError(_("You must make your move before offering a draw"), 0);
12893             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12894         }
12895     } else if (first.offeredDraw) {
12896         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12897     } else {
12898         if (first.sendDrawOffers) {
12899             SendToProgram("draw\n", &first);
12900             userOfferedDraw = TRUE;
12901         }
12902     }
12903 }
12904
12905 void
12906 AdjournEvent()
12907 {
12908     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12909
12910     if (appData.icsActive) {
12911         SendToICS(ics_prefix);
12912         SendToICS("adjourn\n");
12913     } else {
12914         /* Currently GNU Chess doesn't offer or accept Adjourns */
12915     }
12916 }
12917
12918
12919 void
12920 AbortEvent()
12921 {
12922     /* Offer Abort or accept pending Abort offer from opponent */
12923
12924     if (appData.icsActive) {
12925         SendToICS(ics_prefix);
12926         SendToICS("abort\n");
12927     } else {
12928         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12929     }
12930 }
12931
12932 void
12933 ResignEvent()
12934 {
12935     /* Resign.  You can do this even if it's not your turn. */
12936
12937     if (appData.icsActive) {
12938         SendToICS(ics_prefix);
12939         SendToICS("resign\n");
12940     } else {
12941         switch (gameMode) {
12942           case MachinePlaysWhite:
12943             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12944             break;
12945           case MachinePlaysBlack:
12946             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12947             break;
12948           case EditGame:
12949             if (cmailMsgLoaded) {
12950                 TruncateGame();
12951                 if (WhiteOnMove(cmailOldMove)) {
12952                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12953                 } else {
12954                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12955                 }
12956                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12957             }
12958             break;
12959           default:
12960             break;
12961         }
12962     }
12963 }
12964
12965
12966 void
12967 StopObservingEvent()
12968 {
12969     /* Stop observing current games */
12970     SendToICS(ics_prefix);
12971     SendToICS("unobserve\n");
12972 }
12973
12974 void
12975 StopExaminingEvent()
12976 {
12977     /* Stop observing current game */
12978     SendToICS(ics_prefix);
12979     SendToICS("unexamine\n");
12980 }
12981
12982 void
12983 ForwardInner(target)
12984      int target;
12985 {
12986     int limit;
12987
12988     if (appData.debugMode)
12989         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12990                 target, currentMove, forwardMostMove);
12991
12992     if (gameMode == EditPosition)
12993       return;
12994
12995     if (gameMode == PlayFromGameFile && !pausing)
12996       PauseEvent();
12997
12998     if (gameMode == IcsExamining && pausing)
12999       limit = pauseExamForwardMostMove;
13000     else
13001       limit = forwardMostMove;
13002
13003     if (target > limit) target = limit;
13004
13005     if (target > 0 && moveList[target - 1][0]) {
13006         int fromX, fromY, toX, toY;
13007         toX = moveList[target - 1][2] - AAA;
13008         toY = moveList[target - 1][3] - ONE;
13009         if (moveList[target - 1][1] == '@') {
13010             if (appData.highlightLastMove) {
13011                 SetHighlights(-1, -1, toX, toY);
13012             }
13013         } else {
13014             fromX = moveList[target - 1][0] - AAA;
13015             fromY = moveList[target - 1][1] - ONE;
13016             if (target == currentMove + 1) {
13017                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13018             }
13019             if (appData.highlightLastMove) {
13020                 SetHighlights(fromX, fromY, toX, toY);
13021             }
13022         }
13023     }
13024     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13025         gameMode == Training || gameMode == PlayFromGameFile ||
13026         gameMode == AnalyzeFile) {
13027         while (currentMove < target) {
13028             SendMoveToProgram(currentMove++, &first);
13029         }
13030     } else {
13031         currentMove = target;
13032     }
13033
13034     if (gameMode == EditGame || gameMode == EndOfGame) {
13035         whiteTimeRemaining = timeRemaining[0][currentMove];
13036         blackTimeRemaining = timeRemaining[1][currentMove];
13037     }
13038     DisplayBothClocks();
13039     DisplayMove(currentMove - 1);
13040     DrawPosition(FALSE, boards[currentMove]);
13041     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13042     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13043         DisplayComment(currentMove - 1, commentList[currentMove]);
13044     }
13045 }
13046
13047
13048 void
13049 ForwardEvent()
13050 {
13051     if (gameMode == IcsExamining && !pausing) {
13052         SendToICS(ics_prefix);
13053         SendToICS("forward\n");
13054     } else {
13055         ForwardInner(currentMove + 1);
13056     }
13057 }
13058
13059 void
13060 ToEndEvent()
13061 {
13062     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13063         /* to optimze, we temporarily turn off analysis mode while we feed
13064          * the remaining moves to the engine. Otherwise we get analysis output
13065          * after each move.
13066          */
13067         if (first.analysisSupport) {
13068           SendToProgram("exit\nforce\n", &first);
13069           first.analyzing = FALSE;
13070         }
13071     }
13072
13073     if (gameMode == IcsExamining && !pausing) {
13074         SendToICS(ics_prefix);
13075         SendToICS("forward 999999\n");
13076     } else {
13077         ForwardInner(forwardMostMove);
13078     }
13079
13080     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13081         /* we have fed all the moves, so reactivate analysis mode */
13082         SendToProgram("analyze\n", &first);
13083         first.analyzing = TRUE;
13084         /*first.maybeThinking = TRUE;*/
13085         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13086     }
13087 }
13088
13089 void
13090 BackwardInner(target)
13091      int target;
13092 {
13093     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13094
13095     if (appData.debugMode)
13096         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13097                 target, currentMove, forwardMostMove);
13098
13099     if (gameMode == EditPosition) return;
13100     if (currentMove <= backwardMostMove) {
13101         ClearHighlights();
13102         DrawPosition(full_redraw, boards[currentMove]);
13103         return;
13104     }
13105     if (gameMode == PlayFromGameFile && !pausing)
13106       PauseEvent();
13107
13108     if (moveList[target][0]) {
13109         int fromX, fromY, toX, toY;
13110         toX = moveList[target][2] - AAA;
13111         toY = moveList[target][3] - ONE;
13112         if (moveList[target][1] == '@') {
13113             if (appData.highlightLastMove) {
13114                 SetHighlights(-1, -1, toX, toY);
13115             }
13116         } else {
13117             fromX = moveList[target][0] - AAA;
13118             fromY = moveList[target][1] - ONE;
13119             if (target == currentMove - 1) {
13120                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13121             }
13122             if (appData.highlightLastMove) {
13123                 SetHighlights(fromX, fromY, toX, toY);
13124             }
13125         }
13126     }
13127     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13128         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13129         while (currentMove > target) {
13130             SendToProgram("undo\n", &first);
13131             currentMove--;
13132         }
13133     } else {
13134         currentMove = target;
13135     }
13136
13137     if (gameMode == EditGame || gameMode == EndOfGame) {
13138         whiteTimeRemaining = timeRemaining[0][currentMove];
13139         blackTimeRemaining = timeRemaining[1][currentMove];
13140     }
13141     DisplayBothClocks();
13142     DisplayMove(currentMove - 1);
13143     DrawPosition(full_redraw, boards[currentMove]);
13144     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13145     // [HGM] PV info: routine tests if comment empty
13146     DisplayComment(currentMove - 1, commentList[currentMove]);
13147 }
13148
13149 void
13150 BackwardEvent()
13151 {
13152     if (gameMode == IcsExamining && !pausing) {
13153         SendToICS(ics_prefix);
13154         SendToICS("backward\n");
13155     } else {
13156         BackwardInner(currentMove - 1);
13157     }
13158 }
13159
13160 void
13161 ToStartEvent()
13162 {
13163     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13164         /* to optimize, we temporarily turn off analysis mode while we undo
13165          * all the moves. Otherwise we get analysis output after each undo.
13166          */
13167         if (first.analysisSupport) {
13168           SendToProgram("exit\nforce\n", &first);
13169           first.analyzing = FALSE;
13170         }
13171     }
13172
13173     if (gameMode == IcsExamining && !pausing) {
13174         SendToICS(ics_prefix);
13175         SendToICS("backward 999999\n");
13176     } else {
13177         BackwardInner(backwardMostMove);
13178     }
13179
13180     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13181         /* we have fed all the moves, so reactivate analysis mode */
13182         SendToProgram("analyze\n", &first);
13183         first.analyzing = TRUE;
13184         /*first.maybeThinking = TRUE;*/
13185         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13186     }
13187 }
13188
13189 void
13190 ToNrEvent(int to)
13191 {
13192   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13193   if (to >= forwardMostMove) to = forwardMostMove;
13194   if (to <= backwardMostMove) to = backwardMostMove;
13195   if (to < currentMove) {
13196     BackwardInner(to);
13197   } else {
13198     ForwardInner(to);
13199   }
13200 }
13201
13202 void
13203 RevertEvent(Boolean annotate)
13204 {
13205     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13206         return;
13207     }
13208     if (gameMode != IcsExamining) {
13209         DisplayError(_("You are not examining a game"), 0);
13210         return;
13211     }
13212     if (pausing) {
13213         DisplayError(_("You can't revert while pausing"), 0);
13214         return;
13215     }
13216     SendToICS(ics_prefix);
13217     SendToICS("revert\n");
13218 }
13219
13220 void
13221 RetractMoveEvent()
13222 {
13223     switch (gameMode) {
13224       case MachinePlaysWhite:
13225       case MachinePlaysBlack:
13226         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13227             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13228             return;
13229         }
13230         if (forwardMostMove < 2) return;
13231         currentMove = forwardMostMove = forwardMostMove - 2;
13232         whiteTimeRemaining = timeRemaining[0][currentMove];
13233         blackTimeRemaining = timeRemaining[1][currentMove];
13234         DisplayBothClocks();
13235         DisplayMove(currentMove - 1);
13236         ClearHighlights();/*!! could figure this out*/
13237         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13238         SendToProgram("remove\n", &first);
13239         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13240         break;
13241
13242       case BeginningOfGame:
13243       default:
13244         break;
13245
13246       case IcsPlayingWhite:
13247       case IcsPlayingBlack:
13248         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13249             SendToICS(ics_prefix);
13250             SendToICS("takeback 2\n");
13251         } else {
13252             SendToICS(ics_prefix);
13253             SendToICS("takeback 1\n");
13254         }
13255         break;
13256     }
13257 }
13258
13259 void
13260 MoveNowEvent()
13261 {
13262     ChessProgramState *cps;
13263
13264     switch (gameMode) {
13265       case MachinePlaysWhite:
13266         if (!WhiteOnMove(forwardMostMove)) {
13267             DisplayError(_("It is your turn"), 0);
13268             return;
13269         }
13270         cps = &first;
13271         break;
13272       case MachinePlaysBlack:
13273         if (WhiteOnMove(forwardMostMove)) {
13274             DisplayError(_("It is your turn"), 0);
13275             return;
13276         }
13277         cps = &first;
13278         break;
13279       case TwoMachinesPlay:
13280         if (WhiteOnMove(forwardMostMove) ==
13281             (first.twoMachinesColor[0] == 'w')) {
13282             cps = &first;
13283         } else {
13284             cps = &second;
13285         }
13286         break;
13287       case BeginningOfGame:
13288       default:
13289         return;
13290     }
13291     SendToProgram("?\n", cps);
13292 }
13293
13294 void
13295 TruncateGameEvent()
13296 {
13297     EditGameEvent();
13298     if (gameMode != EditGame) return;
13299     TruncateGame();
13300 }
13301
13302 void
13303 TruncateGame()
13304 {
13305     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13306     if (forwardMostMove > currentMove) {
13307         if (gameInfo.resultDetails != NULL) {
13308             free(gameInfo.resultDetails);
13309             gameInfo.resultDetails = NULL;
13310             gameInfo.result = GameUnfinished;
13311         }
13312         forwardMostMove = currentMove;
13313         HistorySet(parseList, backwardMostMove, forwardMostMove,
13314                    currentMove-1);
13315     }
13316 }
13317
13318 void
13319 HintEvent()
13320 {
13321     if (appData.noChessProgram) return;
13322     switch (gameMode) {
13323       case MachinePlaysWhite:
13324         if (WhiteOnMove(forwardMostMove)) {
13325             DisplayError(_("Wait until your turn"), 0);
13326             return;
13327         }
13328         break;
13329       case BeginningOfGame:
13330       case MachinePlaysBlack:
13331         if (!WhiteOnMove(forwardMostMove)) {
13332             DisplayError(_("Wait until your turn"), 0);
13333             return;
13334         }
13335         break;
13336       default:
13337         DisplayError(_("No hint available"), 0);
13338         return;
13339     }
13340     SendToProgram("hint\n", &first);
13341     hintRequested = TRUE;
13342 }
13343
13344 void
13345 BookEvent()
13346 {
13347     if (appData.noChessProgram) return;
13348     switch (gameMode) {
13349       case MachinePlaysWhite:
13350         if (WhiteOnMove(forwardMostMove)) {
13351             DisplayError(_("Wait until your turn"), 0);
13352             return;
13353         }
13354         break;
13355       case BeginningOfGame:
13356       case MachinePlaysBlack:
13357         if (!WhiteOnMove(forwardMostMove)) {
13358             DisplayError(_("Wait until your turn"), 0);
13359             return;
13360         }
13361         break;
13362       case EditPosition:
13363         EditPositionDone(TRUE);
13364         break;
13365       case TwoMachinesPlay:
13366         return;
13367       default:
13368         break;
13369     }
13370     SendToProgram("bk\n", &first);
13371     bookOutput[0] = NULLCHAR;
13372     bookRequested = TRUE;
13373 }
13374
13375 void
13376 AboutGameEvent()
13377 {
13378     char *tags = PGNTags(&gameInfo);
13379     TagsPopUp(tags, CmailMsg());
13380     free(tags);
13381 }
13382
13383 /* end button procedures */
13384
13385 void
13386 PrintPosition(fp, move)
13387      FILE *fp;
13388      int move;
13389 {
13390     int i, j;
13391
13392     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13393         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13394             char c = PieceToChar(boards[move][i][j]);
13395             fputc(c == 'x' ? '.' : c, fp);
13396             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13397         }
13398     }
13399     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13400       fprintf(fp, "white to play\n");
13401     else
13402       fprintf(fp, "black to play\n");
13403 }
13404
13405 void
13406 PrintOpponents(fp)
13407      FILE *fp;
13408 {
13409     if (gameInfo.white != NULL) {
13410         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13411     } else {
13412         fprintf(fp, "\n");
13413     }
13414 }
13415
13416 /* Find last component of program's own name, using some heuristics */
13417 void
13418 TidyProgramName(prog, host, buf)
13419      char *prog, *host, buf[MSG_SIZ];
13420 {
13421     char *p, *q;
13422     int local = (strcmp(host, "localhost") == 0);
13423     while (!local && (p = strchr(prog, ';')) != NULL) {
13424         p++;
13425         while (*p == ' ') p++;
13426         prog = p;
13427     }
13428     if (*prog == '"' || *prog == '\'') {
13429         q = strchr(prog + 1, *prog);
13430     } else {
13431         q = strchr(prog, ' ');
13432     }
13433     if (q == NULL) q = prog + strlen(prog);
13434     p = q;
13435     while (p >= prog && *p != '/' && *p != '\\') p--;
13436     p++;
13437     if(p == prog && *p == '"') p++;
13438     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13439     memcpy(buf, p, q - p);
13440     buf[q - p] = NULLCHAR;
13441     if (!local) {
13442         strcat(buf, "@");
13443         strcat(buf, host);
13444     }
13445 }
13446
13447 char *
13448 TimeControlTagValue()
13449 {
13450     char buf[MSG_SIZ];
13451     if (!appData.clockMode) {
13452       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13453     } else if (movesPerSession > 0) {
13454       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13455     } else if (timeIncrement == 0) {
13456       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13457     } else {
13458       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13459     }
13460     return StrSave(buf);
13461 }
13462
13463 void
13464 SetGameInfo()
13465 {
13466     /* This routine is used only for certain modes */
13467     VariantClass v = gameInfo.variant;
13468     ChessMove r = GameUnfinished;
13469     char *p = NULL;
13470
13471     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13472         r = gameInfo.result;
13473         p = gameInfo.resultDetails;
13474         gameInfo.resultDetails = NULL;
13475     }
13476     ClearGameInfo(&gameInfo);
13477     gameInfo.variant = v;
13478
13479     switch (gameMode) {
13480       case MachinePlaysWhite:
13481         gameInfo.event = StrSave( appData.pgnEventHeader );
13482         gameInfo.site = StrSave(HostName());
13483         gameInfo.date = PGNDate();
13484         gameInfo.round = StrSave("-");
13485         gameInfo.white = StrSave(first.tidy);
13486         gameInfo.black = StrSave(UserName());
13487         gameInfo.timeControl = TimeControlTagValue();
13488         break;
13489
13490       case MachinePlaysBlack:
13491         gameInfo.event = StrSave( appData.pgnEventHeader );
13492         gameInfo.site = StrSave(HostName());
13493         gameInfo.date = PGNDate();
13494         gameInfo.round = StrSave("-");
13495         gameInfo.white = StrSave(UserName());
13496         gameInfo.black = StrSave(first.tidy);
13497         gameInfo.timeControl = TimeControlTagValue();
13498         break;
13499
13500       case TwoMachinesPlay:
13501         gameInfo.event = StrSave( appData.pgnEventHeader );
13502         gameInfo.site = StrSave(HostName());
13503         gameInfo.date = PGNDate();
13504         if (matchGame > 0) {
13505             char buf[MSG_SIZ];
13506             snprintf(buf, MSG_SIZ, "%d", matchGame);
13507             gameInfo.round = StrSave(buf);
13508         } else {
13509             gameInfo.round = StrSave("-");
13510         }
13511         if (first.twoMachinesColor[0] == 'w') {
13512             gameInfo.white = StrSave(first.tidy);
13513             gameInfo.black = StrSave(second.tidy);
13514         } else {
13515             gameInfo.white = StrSave(second.tidy);
13516             gameInfo.black = StrSave(first.tidy);
13517         }
13518         gameInfo.timeControl = TimeControlTagValue();
13519         break;
13520
13521       case EditGame:
13522         gameInfo.event = StrSave("Edited game");
13523         gameInfo.site = StrSave(HostName());
13524         gameInfo.date = PGNDate();
13525         gameInfo.round = StrSave("-");
13526         gameInfo.white = StrSave("-");
13527         gameInfo.black = StrSave("-");
13528         gameInfo.result = r;
13529         gameInfo.resultDetails = p;
13530         break;
13531
13532       case EditPosition:
13533         gameInfo.event = StrSave("Edited position");
13534         gameInfo.site = StrSave(HostName());
13535         gameInfo.date = PGNDate();
13536         gameInfo.round = StrSave("-");
13537         gameInfo.white = StrSave("-");
13538         gameInfo.black = StrSave("-");
13539         break;
13540
13541       case IcsPlayingWhite:
13542       case IcsPlayingBlack:
13543       case IcsObserving:
13544       case IcsExamining:
13545         break;
13546
13547       case PlayFromGameFile:
13548         gameInfo.event = StrSave("Game from non-PGN file");
13549         gameInfo.site = StrSave(HostName());
13550         gameInfo.date = PGNDate();
13551         gameInfo.round = StrSave("-");
13552         gameInfo.white = StrSave("?");
13553         gameInfo.black = StrSave("?");
13554         break;
13555
13556       default:
13557         break;
13558     }
13559 }
13560
13561 void
13562 ReplaceComment(index, text)
13563      int index;
13564      char *text;
13565 {
13566     int len;
13567     char *p;
13568     float score;
13569
13570     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13571        pvInfoList[index-1].depth == len &&
13572        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13573        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13574     while (*text == '\n') text++;
13575     len = strlen(text);
13576     while (len > 0 && text[len - 1] == '\n') len--;
13577
13578     if (commentList[index] != NULL)
13579       free(commentList[index]);
13580
13581     if (len == 0) {
13582         commentList[index] = NULL;
13583         return;
13584     }
13585   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13586       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13587       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13588     commentList[index] = (char *) malloc(len + 2);
13589     strncpy(commentList[index], text, len);
13590     commentList[index][len] = '\n';
13591     commentList[index][len + 1] = NULLCHAR;
13592   } else {
13593     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13594     char *p;
13595     commentList[index] = (char *) malloc(len + 7);
13596     safeStrCpy(commentList[index], "{\n", 3);
13597     safeStrCpy(commentList[index]+2, text, len+1);
13598     commentList[index][len+2] = NULLCHAR;
13599     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13600     strcat(commentList[index], "\n}\n");
13601   }
13602 }
13603
13604 void
13605 CrushCRs(text)
13606      char *text;
13607 {
13608   char *p = text;
13609   char *q = text;
13610   char ch;
13611
13612   do {
13613     ch = *p++;
13614     if (ch == '\r') continue;
13615     *q++ = ch;
13616   } while (ch != '\0');
13617 }
13618
13619 void
13620 AppendComment(index, text, addBraces)
13621      int index;
13622      char *text;
13623      Boolean addBraces; // [HGM] braces: tells if we should add {}
13624 {
13625     int oldlen, len;
13626     char *old;
13627
13628 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13629     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13630
13631     CrushCRs(text);
13632     while (*text == '\n') text++;
13633     len = strlen(text);
13634     while (len > 0 && text[len - 1] == '\n') len--;
13635
13636     if (len == 0) return;
13637
13638     if (commentList[index] != NULL) {
13639         old = commentList[index];
13640         oldlen = strlen(old);
13641         while(commentList[index][oldlen-1] ==  '\n')
13642           commentList[index][--oldlen] = NULLCHAR;
13643         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13644         safeStrCpy(commentList[index], old, oldlen + len + 6);
13645         free(old);
13646         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13647         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13648           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13649           while (*text == '\n') { text++; len--; }
13650           commentList[index][--oldlen] = NULLCHAR;
13651       }
13652         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13653         else          strcat(commentList[index], "\n");
13654         strcat(commentList[index], text);
13655         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13656         else          strcat(commentList[index], "\n");
13657     } else {
13658         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13659         if(addBraces)
13660           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13661         else commentList[index][0] = NULLCHAR;
13662         strcat(commentList[index], text);
13663         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13664         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13665     }
13666 }
13667
13668 static char * FindStr( char * text, char * sub_text )
13669 {
13670     char * result = strstr( text, sub_text );
13671
13672     if( result != NULL ) {
13673         result += strlen( sub_text );
13674     }
13675
13676     return result;
13677 }
13678
13679 /* [AS] Try to extract PV info from PGN comment */
13680 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13681 char *GetInfoFromComment( int index, char * text )
13682 {
13683     char * sep = text, *p;
13684
13685     if( text != NULL && index > 0 ) {
13686         int score = 0;
13687         int depth = 0;
13688         int time = -1, sec = 0, deci;
13689         char * s_eval = FindStr( text, "[%eval " );
13690         char * s_emt = FindStr( text, "[%emt " );
13691
13692         if( s_eval != NULL || s_emt != NULL ) {
13693             /* New style */
13694             char delim;
13695
13696             if( s_eval != NULL ) {
13697                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13698                     return text;
13699                 }
13700
13701                 if( delim != ']' ) {
13702                     return text;
13703                 }
13704             }
13705
13706             if( s_emt != NULL ) {
13707             }
13708                 return text;
13709         }
13710         else {
13711             /* We expect something like: [+|-]nnn.nn/dd */
13712             int score_lo = 0;
13713
13714             if(*text != '{') return text; // [HGM] braces: must be normal comment
13715
13716             sep = strchr( text, '/' );
13717             if( sep == NULL || sep < (text+4) ) {
13718                 return text;
13719             }
13720
13721             p = text;
13722             if(p[1] == '(') { // comment starts with PV
13723                p = strchr(p, ')'); // locate end of PV
13724                if(p == NULL || sep < p+5) return text;
13725                // at this point we have something like "{(.*) +0.23/6 ..."
13726                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13727                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13728                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13729             }
13730             time = -1; sec = -1; deci = -1;
13731             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13732                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13733                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13734                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13735                 return text;
13736             }
13737
13738             if( score_lo < 0 || score_lo >= 100 ) {
13739                 return text;
13740             }
13741
13742             if(sec >= 0) time = 600*time + 10*sec; else
13743             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13744
13745             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13746
13747             /* [HGM] PV time: now locate end of PV info */
13748             while( *++sep >= '0' && *sep <= '9'); // strip depth
13749             if(time >= 0)
13750             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13751             if(sec >= 0)
13752             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13753             if(deci >= 0)
13754             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13755             while(*sep == ' ') sep++;
13756         }
13757
13758         if( depth <= 0 ) {
13759             return text;
13760         }
13761
13762         if( time < 0 ) {
13763             time = -1;
13764         }
13765
13766         pvInfoList[index-1].depth = depth;
13767         pvInfoList[index-1].score = score;
13768         pvInfoList[index-1].time  = 10*time; // centi-sec
13769         if(*sep == '}') *sep = 0; else *--sep = '{';
13770         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13771     }
13772     return sep;
13773 }
13774
13775 void
13776 SendToProgram(message, cps)
13777      char *message;
13778      ChessProgramState *cps;
13779 {
13780     int count, outCount, error;
13781     char buf[MSG_SIZ];
13782
13783     if (cps->pr == NULL) return;
13784     Attention(cps);
13785
13786     if (appData.debugMode) {
13787         TimeMark now;
13788         GetTimeMark(&now);
13789         fprintf(debugFP, "%ld >%-6s: %s",
13790                 SubtractTimeMarks(&now, &programStartTime),
13791                 cps->which, message);
13792     }
13793
13794     count = strlen(message);
13795     outCount = OutputToProcess(cps->pr, message, count, &error);
13796     if (outCount < count && !exiting
13797                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13798       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13799       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13800         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13801             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13802                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13803                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13804             } else {
13805                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13806             }
13807             gameInfo.resultDetails = StrSave(buf);
13808         }
13809         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13810     }
13811 }
13812
13813 void
13814 ReceiveFromProgram(isr, closure, message, count, error)
13815      InputSourceRef isr;
13816      VOIDSTAR closure;
13817      char *message;
13818      int count;
13819      int error;
13820 {
13821     char *end_str;
13822     char buf[MSG_SIZ];
13823     ChessProgramState *cps = (ChessProgramState *)closure;
13824
13825     if (isr != cps->isr) return; /* Killed intentionally */
13826     if (count <= 0) {
13827         if (count == 0) {
13828             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13829                     _(cps->which), cps->program);
13830         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13831                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13832                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13833                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13834                 } else {
13835                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13836                 }
13837                 gameInfo.resultDetails = StrSave(buf);
13838             }
13839             RemoveInputSource(cps->isr);
13840             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13841         } else {
13842             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13843                     _(cps->which), cps->program);
13844             RemoveInputSource(cps->isr);
13845
13846             /* [AS] Program is misbehaving badly... kill it */
13847             if( count == -2 ) {
13848                 DestroyChildProcess( cps->pr, 9 );
13849                 cps->pr = NoProc;
13850             }
13851
13852             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13853         }
13854         return;
13855     }
13856
13857     if ((end_str = strchr(message, '\r')) != NULL)
13858       *end_str = NULLCHAR;
13859     if ((end_str = strchr(message, '\n')) != NULL)
13860       *end_str = NULLCHAR;
13861
13862     if (appData.debugMode) {
13863         TimeMark now; int print = 1;
13864         char *quote = ""; char c; int i;
13865
13866         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13867                 char start = message[0];
13868                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13869                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13870                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13871                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13872                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13873                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13874                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13875                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13876                    sscanf(message, "hint: %c", &c)!=1 && 
13877                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13878                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13879                     print = (appData.engineComments >= 2);
13880                 }
13881                 message[0] = start; // restore original message
13882         }
13883         if(print) {
13884                 GetTimeMark(&now);
13885                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13886                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13887                         quote,
13888                         message);
13889         }
13890     }
13891
13892     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13893     if (appData.icsEngineAnalyze) {
13894         if (strstr(message, "whisper") != NULL ||
13895              strstr(message, "kibitz") != NULL ||
13896             strstr(message, "tellics") != NULL) return;
13897     }
13898
13899     HandleMachineMove(message, cps);
13900 }
13901
13902
13903 void
13904 SendTimeControl(cps, mps, tc, inc, sd, st)
13905      ChessProgramState *cps;
13906      int mps, inc, sd, st;
13907      long tc;
13908 {
13909     char buf[MSG_SIZ];
13910     int seconds;
13911
13912     if( timeControl_2 > 0 ) {
13913         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13914             tc = timeControl_2;
13915         }
13916     }
13917     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13918     inc /= cps->timeOdds;
13919     st  /= cps->timeOdds;
13920
13921     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13922
13923     if (st > 0) {
13924       /* Set exact time per move, normally using st command */
13925       if (cps->stKludge) {
13926         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13927         seconds = st % 60;
13928         if (seconds == 0) {
13929           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13930         } else {
13931           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13932         }
13933       } else {
13934         snprintf(buf, MSG_SIZ, "st %d\n", st);
13935       }
13936     } else {
13937       /* Set conventional or incremental time control, using level command */
13938       if (seconds == 0) {
13939         /* Note old gnuchess bug -- minutes:seconds used to not work.
13940            Fixed in later versions, but still avoid :seconds
13941            when seconds is 0. */
13942         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13943       } else {
13944         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13945                  seconds, inc/1000.);
13946       }
13947     }
13948     SendToProgram(buf, cps);
13949
13950     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13951     /* Orthogonally, limit search to given depth */
13952     if (sd > 0) {
13953       if (cps->sdKludge) {
13954         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13955       } else {
13956         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13957       }
13958       SendToProgram(buf, cps);
13959     }
13960
13961     if(cps->nps >= 0) { /* [HGM] nps */
13962         if(cps->supportsNPS == FALSE)
13963           cps->nps = -1; // don't use if engine explicitly says not supported!
13964         else {
13965           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13966           SendToProgram(buf, cps);
13967         }
13968     }
13969 }
13970
13971 ChessProgramState *WhitePlayer()
13972 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13973 {
13974     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13975        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13976         return &second;
13977     return &first;
13978 }
13979
13980 void
13981 SendTimeRemaining(cps, machineWhite)
13982      ChessProgramState *cps;
13983      int /*boolean*/ machineWhite;
13984 {
13985     char message[MSG_SIZ];
13986     long time, otime;
13987
13988     /* Note: this routine must be called when the clocks are stopped
13989        or when they have *just* been set or switched; otherwise
13990        it will be off by the time since the current tick started.
13991     */
13992     if (machineWhite) {
13993         time = whiteTimeRemaining / 10;
13994         otime = blackTimeRemaining / 10;
13995     } else {
13996         time = blackTimeRemaining / 10;
13997         otime = whiteTimeRemaining / 10;
13998     }
13999     /* [HGM] translate opponent's time by time-odds factor */
14000     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14001     if (appData.debugMode) {
14002         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14003     }
14004
14005     if (time <= 0) time = 1;
14006     if (otime <= 0) otime = 1;
14007
14008     snprintf(message, MSG_SIZ, "time %ld\n", time);
14009     SendToProgram(message, cps);
14010
14011     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14012     SendToProgram(message, cps);
14013 }
14014
14015 int
14016 BoolFeature(p, name, loc, cps)
14017      char **p;
14018      char *name;
14019      int *loc;
14020      ChessProgramState *cps;
14021 {
14022   char buf[MSG_SIZ];
14023   int len = strlen(name);
14024   int val;
14025
14026   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14027     (*p) += len + 1;
14028     sscanf(*p, "%d", &val);
14029     *loc = (val != 0);
14030     while (**p && **p != ' ')
14031       (*p)++;
14032     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14033     SendToProgram(buf, cps);
14034     return TRUE;
14035   }
14036   return FALSE;
14037 }
14038
14039 int
14040 IntFeature(p, name, loc, cps)
14041      char **p;
14042      char *name;
14043      int *loc;
14044      ChessProgramState *cps;
14045 {
14046   char buf[MSG_SIZ];
14047   int len = strlen(name);
14048   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14049     (*p) += len + 1;
14050     sscanf(*p, "%d", loc);
14051     while (**p && **p != ' ') (*p)++;
14052     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14053     SendToProgram(buf, cps);
14054     return TRUE;
14055   }
14056   return FALSE;
14057 }
14058
14059 int
14060 StringFeature(p, name, loc, cps)
14061      char **p;
14062      char *name;
14063      char loc[];
14064      ChessProgramState *cps;
14065 {
14066   char buf[MSG_SIZ];
14067   int len = strlen(name);
14068   if (strncmp((*p), name, len) == 0
14069       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14070     (*p) += len + 2;
14071     sscanf(*p, "%[^\"]", loc);
14072     while (**p && **p != '\"') (*p)++;
14073     if (**p == '\"') (*p)++;
14074     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14075     SendToProgram(buf, cps);
14076     return TRUE;
14077   }
14078   return FALSE;
14079 }
14080
14081 int
14082 ParseOption(Option *opt, ChessProgramState *cps)
14083 // [HGM] options: process the string that defines an engine option, and determine
14084 // name, type, default value, and allowed value range
14085 {
14086         char *p, *q, buf[MSG_SIZ];
14087         int n, min = (-1)<<31, max = 1<<31, def;
14088
14089         if(p = strstr(opt->name, " -spin ")) {
14090             if((n = sscanf(p, " -spin %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;
14098         } else if((p = strstr(opt->name, " -slider "))) {
14099             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14100             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14101             if(max < min) max = min; // enforce consistency
14102             if(def < min) def = min;
14103             if(def > max) def = max;
14104             opt->value = def;
14105             opt->min = min;
14106             opt->max = max;
14107             opt->type = Spin; // Slider;
14108         } else if((p = strstr(opt->name, " -string "))) {
14109             opt->textValue = p+9;
14110             opt->type = TextBox;
14111         } else if((p = strstr(opt->name, " -file "))) {
14112             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14113             opt->textValue = p+7;
14114             opt->type = FileName; // FileName;
14115         } else if((p = strstr(opt->name, " -path "))) {
14116             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14117             opt->textValue = p+7;
14118             opt->type = PathName; // PathName;
14119         } else if(p = strstr(opt->name, " -check ")) {
14120             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14121             opt->value = (def != 0);
14122             opt->type = CheckBox;
14123         } else if(p = strstr(opt->name, " -combo ")) {
14124             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14125             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14126             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14127             opt->value = n = 0;
14128             while(q = StrStr(q, " /// ")) {
14129                 n++; *q = 0;    // count choices, and null-terminate each of them
14130                 q += 5;
14131                 if(*q == '*') { // remember default, which is marked with * prefix
14132                     q++;
14133                     opt->value = n;
14134                 }
14135                 cps->comboList[cps->comboCnt++] = q;
14136             }
14137             cps->comboList[cps->comboCnt++] = NULL;
14138             opt->max = n + 1;
14139             opt->type = ComboBox;
14140         } else if(p = strstr(opt->name, " -button")) {
14141             opt->type = Button;
14142         } else if(p = strstr(opt->name, " -save")) {
14143             opt->type = SaveButton;
14144         } else return FALSE;
14145         *p = 0; // terminate option name
14146         // now look if the command-line options define a setting for this engine option.
14147         if(cps->optionSettings && cps->optionSettings[0])
14148             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14149         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14150           snprintf(buf, MSG_SIZ, "option %s", p);
14151                 if(p = strstr(buf, ",")) *p = 0;
14152                 if(q = strchr(buf, '=')) switch(opt->type) {
14153                     case ComboBox:
14154                         for(n=0; n<opt->max; n++)
14155                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14156                         break;
14157                     case TextBox:
14158                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14159                         break;
14160                     case Spin:
14161                     case CheckBox:
14162                         opt->value = atoi(q+1);
14163                     default:
14164                         break;
14165                 }
14166                 strcat(buf, "\n");
14167                 SendToProgram(buf, cps);
14168         }
14169         return TRUE;
14170 }
14171
14172 void
14173 FeatureDone(cps, val)
14174      ChessProgramState* cps;
14175      int val;
14176 {
14177   DelayedEventCallback cb = GetDelayedEvent();
14178   if ((cb == InitBackEnd3 && cps == &first) ||
14179       (cb == SettingsMenuIfReady && cps == &second) ||
14180       (cb == LoadEngine) ||
14181       (cb == TwoMachinesEventIfReady && cps == &second)) {
14182     CancelDelayedEvent();
14183     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14184   }
14185   cps->initDone = val;
14186 }
14187
14188 /* Parse feature command from engine */
14189 void
14190 ParseFeatures(args, cps)
14191      char* args;
14192      ChessProgramState *cps;
14193 {
14194   char *p = args;
14195   char *q;
14196   int val;
14197   char buf[MSG_SIZ];
14198
14199   for (;;) {
14200     while (*p == ' ') p++;
14201     if (*p == NULLCHAR) return;
14202
14203     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14204     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14205     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14206     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14207     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14208     if (BoolFeature(&p, "reuse", &val, cps)) {
14209       /* Engine can disable reuse, but can't enable it if user said no */
14210       if (!val) cps->reuse = FALSE;
14211       continue;
14212     }
14213     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14214     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14215       if (gameMode == TwoMachinesPlay) {
14216         DisplayTwoMachinesTitle();
14217       } else {
14218         DisplayTitle("");
14219       }
14220       continue;
14221     }
14222     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14223     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14224     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14225     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14226     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14227     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14228     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14229     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14230     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14231     if (IntFeature(&p, "done", &val, cps)) {
14232       FeatureDone(cps, val);
14233       continue;
14234     }
14235     /* Added by Tord: */
14236     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14237     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14238     /* End of additions by Tord */
14239
14240     /* [HGM] added features: */
14241     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14242     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14243     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14244     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14245     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14246     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14247     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14248         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14249           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14250             SendToProgram(buf, cps);
14251             continue;
14252         }
14253         if(cps->nrOptions >= MAX_OPTIONS) {
14254             cps->nrOptions--;
14255             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14256             DisplayError(buf, 0);
14257         }
14258         continue;
14259     }
14260     /* End of additions by HGM */
14261
14262     /* unknown feature: complain and skip */
14263     q = p;
14264     while (*q && *q != '=') q++;
14265     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14266     SendToProgram(buf, cps);
14267     p = q;
14268     if (*p == '=') {
14269       p++;
14270       if (*p == '\"') {
14271         p++;
14272         while (*p && *p != '\"') p++;
14273         if (*p == '\"') p++;
14274       } else {
14275         while (*p && *p != ' ') p++;
14276       }
14277     }
14278   }
14279
14280 }
14281
14282 void
14283 PeriodicUpdatesEvent(newState)
14284      int newState;
14285 {
14286     if (newState == appData.periodicUpdates)
14287       return;
14288
14289     appData.periodicUpdates=newState;
14290
14291     /* Display type changes, so update it now */
14292 //    DisplayAnalysis();
14293
14294     /* Get the ball rolling again... */
14295     if (newState) {
14296         AnalysisPeriodicEvent(1);
14297         StartAnalysisClock();
14298     }
14299 }
14300
14301 void
14302 PonderNextMoveEvent(newState)
14303      int newState;
14304 {
14305     if (newState == appData.ponderNextMove) return;
14306     if (gameMode == EditPosition) EditPositionDone(TRUE);
14307     if (newState) {
14308         SendToProgram("hard\n", &first);
14309         if (gameMode == TwoMachinesPlay) {
14310             SendToProgram("hard\n", &second);
14311         }
14312     } else {
14313         SendToProgram("easy\n", &first);
14314         thinkOutput[0] = NULLCHAR;
14315         if (gameMode == TwoMachinesPlay) {
14316             SendToProgram("easy\n", &second);
14317         }
14318     }
14319     appData.ponderNextMove = newState;
14320 }
14321
14322 void
14323 NewSettingEvent(option, feature, command, value)
14324      char *command;
14325      int option, value, *feature;
14326 {
14327     char buf[MSG_SIZ];
14328
14329     if (gameMode == EditPosition) EditPositionDone(TRUE);
14330     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14331     if(feature == NULL || *feature) SendToProgram(buf, &first);
14332     if (gameMode == TwoMachinesPlay) {
14333         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14334     }
14335 }
14336
14337 void
14338 ShowThinkingEvent()
14339 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14340 {
14341     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14342     int newState = appData.showThinking
14343         // [HGM] thinking: other features now need thinking output as well
14344         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14345
14346     if (oldState == newState) return;
14347     oldState = newState;
14348     if (gameMode == EditPosition) EditPositionDone(TRUE);
14349     if (oldState) {
14350         SendToProgram("post\n", &first);
14351         if (gameMode == TwoMachinesPlay) {
14352             SendToProgram("post\n", &second);
14353         }
14354     } else {
14355         SendToProgram("nopost\n", &first);
14356         thinkOutput[0] = NULLCHAR;
14357         if (gameMode == TwoMachinesPlay) {
14358             SendToProgram("nopost\n", &second);
14359         }
14360     }
14361 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14362 }
14363
14364 void
14365 AskQuestionEvent(title, question, replyPrefix, which)
14366      char *title; char *question; char *replyPrefix; char *which;
14367 {
14368   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14369   if (pr == NoProc) return;
14370   AskQuestion(title, question, replyPrefix, pr);
14371 }
14372
14373 void
14374 TypeInEvent(char firstChar)
14375 {
14376     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14377         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14378         gameMode == AnalyzeMode || gameMode == EditGame || \r
14379         gameMode == EditPosition || gameMode == IcsExamining ||\r
14380         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14381         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14382                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14383                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14384         gameMode == Training) PopUpMoveDialog(firstChar);
14385 }
14386
14387 void
14388 TypeInDoneEvent(char *move)
14389 {
14390         Board board;
14391         int n, fromX, fromY, toX, toY;
14392         char promoChar;
14393         ChessMove moveType;\r
14394
14395         // [HGM] FENedit\r
14396         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14397                 EditPositionPasteFEN(move);\r
14398                 return;\r
14399         }\r
14400         // [HGM] movenum: allow move number to be typed in any mode\r
14401         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14402           ToNrEvent(2*n-1);\r
14403           return;\r
14404         }\r
14405
14406       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14407         gameMode != Training) {\r
14408         DisplayMoveError(_("Displayed move is not current"));\r
14409       } else {\r
14410         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14411           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14412         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14413         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14414           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14415           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14416         } else {\r
14417           DisplayMoveError(_("Could not parse move"));\r
14418         }
14419       }\r
14420 }\r
14421
14422 void
14423 DisplayMove(moveNumber)
14424      int moveNumber;
14425 {
14426     char message[MSG_SIZ];
14427     char res[MSG_SIZ];
14428     char cpThinkOutput[MSG_SIZ];
14429
14430     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14431
14432     if (moveNumber == forwardMostMove - 1 ||
14433         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14434
14435         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14436
14437         if (strchr(cpThinkOutput, '\n')) {
14438             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14439         }
14440     } else {
14441         *cpThinkOutput = NULLCHAR;
14442     }
14443
14444     /* [AS] Hide thinking from human user */
14445     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14446         *cpThinkOutput = NULLCHAR;
14447         if( thinkOutput[0] != NULLCHAR ) {
14448             int i;
14449
14450             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14451                 cpThinkOutput[i] = '.';
14452             }
14453             cpThinkOutput[i] = NULLCHAR;
14454             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14455         }
14456     }
14457
14458     if (moveNumber == forwardMostMove - 1 &&
14459         gameInfo.resultDetails != NULL) {
14460         if (gameInfo.resultDetails[0] == NULLCHAR) {
14461           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14462         } else {
14463           snprintf(res, MSG_SIZ, " {%s} %s",
14464                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14465         }
14466     } else {
14467         res[0] = NULLCHAR;
14468     }
14469
14470     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14471         DisplayMessage(res, cpThinkOutput);
14472     } else {
14473       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14474                 WhiteOnMove(moveNumber) ? " " : ".. ",
14475                 parseList[moveNumber], res);
14476         DisplayMessage(message, cpThinkOutput);
14477     }
14478 }
14479
14480 void
14481 DisplayComment(moveNumber, text)
14482      int moveNumber;
14483      char *text;
14484 {
14485     char title[MSG_SIZ];
14486     char buf[8000]; // comment can be long!
14487     int score, depth;
14488
14489     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14490       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14491     } else {
14492       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14493               WhiteOnMove(moveNumber) ? " " : ".. ",
14494               parseList[moveNumber]);
14495     }
14496     // [HGM] PV info: display PV info together with (or as) comment
14497     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14498       if(text == NULL) text = "";
14499       score = pvInfoList[moveNumber].score;
14500       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14501               depth, (pvInfoList[moveNumber].time+50)/100, text);
14502       text = buf;
14503     }
14504     if (text != NULL && (appData.autoDisplayComment || commentUp))
14505         CommentPopUp(title, text);
14506 }
14507
14508 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14509  * might be busy thinking or pondering.  It can be omitted if your
14510  * gnuchess is configured to stop thinking immediately on any user
14511  * input.  However, that gnuchess feature depends on the FIONREAD
14512  * ioctl, which does not work properly on some flavors of Unix.
14513  */
14514 void
14515 Attention(cps)
14516      ChessProgramState *cps;
14517 {
14518 #if ATTENTION
14519     if (!cps->useSigint) return;
14520     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14521     switch (gameMode) {
14522       case MachinePlaysWhite:
14523       case MachinePlaysBlack:
14524       case TwoMachinesPlay:
14525       case IcsPlayingWhite:
14526       case IcsPlayingBlack:
14527       case AnalyzeMode:
14528       case AnalyzeFile:
14529         /* Skip if we know it isn't thinking */
14530         if (!cps->maybeThinking) return;
14531         if (appData.debugMode)
14532           fprintf(debugFP, "Interrupting %s\n", cps->which);
14533         InterruptChildProcess(cps->pr);
14534         cps->maybeThinking = FALSE;
14535         break;
14536       default:
14537         break;
14538     }
14539 #endif /*ATTENTION*/
14540 }
14541
14542 int
14543 CheckFlags()
14544 {
14545     if (whiteTimeRemaining <= 0) {
14546         if (!whiteFlag) {
14547             whiteFlag = TRUE;
14548             if (appData.icsActive) {
14549                 if (appData.autoCallFlag &&
14550                     gameMode == IcsPlayingBlack && !blackFlag) {
14551                   SendToICS(ics_prefix);
14552                   SendToICS("flag\n");
14553                 }
14554             } else {
14555                 if (blackFlag) {
14556                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14557                 } else {
14558                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14559                     if (appData.autoCallFlag) {
14560                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14561                         return TRUE;
14562                     }
14563                 }
14564             }
14565         }
14566     }
14567     if (blackTimeRemaining <= 0) {
14568         if (!blackFlag) {
14569             blackFlag = TRUE;
14570             if (appData.icsActive) {
14571                 if (appData.autoCallFlag &&
14572                     gameMode == IcsPlayingWhite && !whiteFlag) {
14573                   SendToICS(ics_prefix);
14574                   SendToICS("flag\n");
14575                 }
14576             } else {
14577                 if (whiteFlag) {
14578                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14579                 } else {
14580                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14581                     if (appData.autoCallFlag) {
14582                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14583                         return TRUE;
14584                     }
14585                 }
14586             }
14587         }
14588     }
14589     return FALSE;
14590 }
14591
14592 void
14593 CheckTimeControl()
14594 {
14595     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14596         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14597
14598     /*
14599      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14600      */
14601     if ( !WhiteOnMove(forwardMostMove) ) {
14602         /* White made time control */
14603         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14604         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14605         /* [HGM] time odds: correct new time quota for time odds! */
14606                                             / WhitePlayer()->timeOdds;
14607         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14608     } else {
14609         lastBlack -= blackTimeRemaining;
14610         /* Black made time control */
14611         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14612                                             / WhitePlayer()->other->timeOdds;
14613         lastWhite = whiteTimeRemaining;
14614     }
14615 }
14616
14617 void
14618 DisplayBothClocks()
14619 {
14620     int wom = gameMode == EditPosition ?
14621       !blackPlaysFirst : WhiteOnMove(currentMove);
14622     DisplayWhiteClock(whiteTimeRemaining, wom);
14623     DisplayBlackClock(blackTimeRemaining, !wom);
14624 }
14625
14626
14627 /* Timekeeping seems to be a portability nightmare.  I think everyone
14628    has ftime(), but I'm really not sure, so I'm including some ifdefs
14629    to use other calls if you don't.  Clocks will be less accurate if
14630    you have neither ftime nor gettimeofday.
14631 */
14632
14633 /* VS 2008 requires the #include outside of the function */
14634 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14635 #include <sys/timeb.h>
14636 #endif
14637
14638 /* Get the current time as a TimeMark */
14639 void
14640 GetTimeMark(tm)
14641      TimeMark *tm;
14642 {
14643 #if HAVE_GETTIMEOFDAY
14644
14645     struct timeval timeVal;
14646     struct timezone timeZone;
14647
14648     gettimeofday(&timeVal, &timeZone);
14649     tm->sec = (long) timeVal.tv_sec;
14650     tm->ms = (int) (timeVal.tv_usec / 1000L);
14651
14652 #else /*!HAVE_GETTIMEOFDAY*/
14653 #if HAVE_FTIME
14654
14655 // include <sys/timeb.h> / moved to just above start of function
14656     struct timeb timeB;
14657
14658     ftime(&timeB);
14659     tm->sec = (long) timeB.time;
14660     tm->ms = (int) timeB.millitm;
14661
14662 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14663     tm->sec = (long) time(NULL);
14664     tm->ms = 0;
14665 #endif
14666 #endif
14667 }
14668
14669 /* Return the difference in milliseconds between two
14670    time marks.  We assume the difference will fit in a long!
14671 */
14672 long
14673 SubtractTimeMarks(tm2, tm1)
14674      TimeMark *tm2, *tm1;
14675 {
14676     return 1000L*(tm2->sec - tm1->sec) +
14677            (long) (tm2->ms - tm1->ms);
14678 }
14679
14680
14681 /*
14682  * Code to manage the game clocks.
14683  *
14684  * In tournament play, black starts the clock and then white makes a move.
14685  * We give the human user a slight advantage if he is playing white---the
14686  * clocks don't run until he makes his first move, so it takes zero time.
14687  * Also, we don't account for network lag, so we could get out of sync
14688  * with GNU Chess's clock -- but then, referees are always right.
14689  */
14690
14691 static TimeMark tickStartTM;
14692 static long intendedTickLength;
14693
14694 long
14695 NextTickLength(timeRemaining)
14696      long timeRemaining;
14697 {
14698     long nominalTickLength, nextTickLength;
14699
14700     if (timeRemaining > 0L && timeRemaining <= 10000L)
14701       nominalTickLength = 100L;
14702     else
14703       nominalTickLength = 1000L;
14704     nextTickLength = timeRemaining % nominalTickLength;
14705     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14706
14707     return nextTickLength;
14708 }
14709
14710 /* Adjust clock one minute up or down */
14711 void
14712 AdjustClock(Boolean which, int dir)
14713 {
14714     if(which) blackTimeRemaining += 60000*dir;
14715     else      whiteTimeRemaining += 60000*dir;
14716     DisplayBothClocks();
14717 }
14718
14719 /* Stop clocks and reset to a fresh time control */
14720 void
14721 ResetClocks()
14722 {
14723     (void) StopClockTimer();
14724     if (appData.icsActive) {
14725         whiteTimeRemaining = blackTimeRemaining = 0;
14726     } else if (searchTime) {
14727         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14728         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14729     } else { /* [HGM] correct new time quote for time odds */
14730         whiteTC = blackTC = fullTimeControlString;
14731         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14732         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14733     }
14734     if (whiteFlag || blackFlag) {
14735         DisplayTitle("");
14736         whiteFlag = blackFlag = FALSE;
14737     }
14738     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14739     DisplayBothClocks();
14740 }
14741
14742 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14743
14744 /* Decrement running clock by amount of time that has passed */
14745 void
14746 DecrementClocks()
14747 {
14748     long timeRemaining;
14749     long lastTickLength, fudge;
14750     TimeMark now;
14751
14752     if (!appData.clockMode) return;
14753     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14754
14755     GetTimeMark(&now);
14756
14757     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14758
14759     /* Fudge if we woke up a little too soon */
14760     fudge = intendedTickLength - lastTickLength;
14761     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14762
14763     if (WhiteOnMove(forwardMostMove)) {
14764         if(whiteNPS >= 0) lastTickLength = 0;
14765         timeRemaining = whiteTimeRemaining -= lastTickLength;
14766         if(timeRemaining < 0 && !appData.icsActive) {
14767             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14768             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14769                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14770                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14771             }
14772         }
14773         DisplayWhiteClock(whiteTimeRemaining - fudge,
14774                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14775     } else {
14776         if(blackNPS >= 0) lastTickLength = 0;
14777         timeRemaining = blackTimeRemaining -= lastTickLength;
14778         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14779             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14780             if(suddenDeath) {
14781                 blackStartMove = forwardMostMove;
14782                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14783             }
14784         }
14785         DisplayBlackClock(blackTimeRemaining - fudge,
14786                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14787     }
14788     if (CheckFlags()) return;
14789
14790     tickStartTM = now;
14791     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14792     StartClockTimer(intendedTickLength);
14793
14794     /* if the time remaining has fallen below the alarm threshold, sound the
14795      * alarm. if the alarm has sounded and (due to a takeback or time control
14796      * with increment) the time remaining has increased to a level above the
14797      * threshold, reset the alarm so it can sound again.
14798      */
14799
14800     if (appData.icsActive && appData.icsAlarm) {
14801
14802         /* make sure we are dealing with the user's clock */
14803         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14804                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14805            )) return;
14806
14807         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14808             alarmSounded = FALSE;
14809         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14810             PlayAlarmSound();
14811             alarmSounded = TRUE;
14812         }
14813     }
14814 }
14815
14816
14817 /* A player has just moved, so stop the previously running
14818    clock and (if in clock mode) start the other one.
14819    We redisplay both clocks in case we're in ICS mode, because
14820    ICS gives us an update to both clocks after every move.
14821    Note that this routine is called *after* forwardMostMove
14822    is updated, so the last fractional tick must be subtracted
14823    from the color that is *not* on move now.
14824 */
14825 void
14826 SwitchClocks(int newMoveNr)
14827 {
14828     long lastTickLength;
14829     TimeMark now;
14830     int flagged = FALSE;
14831
14832     GetTimeMark(&now);
14833
14834     if (StopClockTimer() && appData.clockMode) {
14835         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14836         if (!WhiteOnMove(forwardMostMove)) {
14837             if(blackNPS >= 0) lastTickLength = 0;
14838             blackTimeRemaining -= lastTickLength;
14839            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14840 //         if(pvInfoList[forwardMostMove].time == -1)
14841                  pvInfoList[forwardMostMove].time =               // use GUI time
14842                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14843         } else {
14844            if(whiteNPS >= 0) lastTickLength = 0;
14845            whiteTimeRemaining -= lastTickLength;
14846            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14847 //         if(pvInfoList[forwardMostMove].time == -1)
14848                  pvInfoList[forwardMostMove].time =
14849                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14850         }
14851         flagged = CheckFlags();
14852     }
14853     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14854     CheckTimeControl();
14855
14856     if (flagged || !appData.clockMode) return;
14857
14858     switch (gameMode) {
14859       case MachinePlaysBlack:
14860       case MachinePlaysWhite:
14861       case BeginningOfGame:
14862         if (pausing) return;
14863         break;
14864
14865       case EditGame:
14866       case PlayFromGameFile:
14867       case IcsExamining:
14868         return;
14869
14870       default:
14871         break;
14872     }
14873
14874     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14875         if(WhiteOnMove(forwardMostMove))
14876              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14877         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14878     }
14879
14880     tickStartTM = now;
14881     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14882       whiteTimeRemaining : blackTimeRemaining);
14883     StartClockTimer(intendedTickLength);
14884 }
14885
14886
14887 /* Stop both clocks */
14888 void
14889 StopClocks()
14890 {
14891     long lastTickLength;
14892     TimeMark now;
14893
14894     if (!StopClockTimer()) return;
14895     if (!appData.clockMode) return;
14896
14897     GetTimeMark(&now);
14898
14899     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14900     if (WhiteOnMove(forwardMostMove)) {
14901         if(whiteNPS >= 0) lastTickLength = 0;
14902         whiteTimeRemaining -= lastTickLength;
14903         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14904     } else {
14905         if(blackNPS >= 0) lastTickLength = 0;
14906         blackTimeRemaining -= lastTickLength;
14907         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14908     }
14909     CheckFlags();
14910 }
14911
14912 /* Start clock of player on move.  Time may have been reset, so
14913    if clock is already running, stop and restart it. */
14914 void
14915 StartClocks()
14916 {
14917     (void) StopClockTimer(); /* in case it was running already */
14918     DisplayBothClocks();
14919     if (CheckFlags()) return;
14920
14921     if (!appData.clockMode) return;
14922     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14923
14924     GetTimeMark(&tickStartTM);
14925     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14926       whiteTimeRemaining : blackTimeRemaining);
14927
14928    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14929     whiteNPS = blackNPS = -1;
14930     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14931        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14932         whiteNPS = first.nps;
14933     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14934        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14935         blackNPS = first.nps;
14936     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14937         whiteNPS = second.nps;
14938     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14939         blackNPS = second.nps;
14940     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14941
14942     StartClockTimer(intendedTickLength);
14943 }
14944
14945 char *
14946 TimeString(ms)
14947      long ms;
14948 {
14949     long second, minute, hour, day;
14950     char *sign = "";
14951     static char buf[32];
14952
14953     if (ms > 0 && ms <= 9900) {
14954       /* convert milliseconds to tenths, rounding up */
14955       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14956
14957       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14958       return buf;
14959     }
14960
14961     /* convert milliseconds to seconds, rounding up */
14962     /* use floating point to avoid strangeness of integer division
14963        with negative dividends on many machines */
14964     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14965
14966     if (second < 0) {
14967         sign = "-";
14968         second = -second;
14969     }
14970
14971     day = second / (60 * 60 * 24);
14972     second = second % (60 * 60 * 24);
14973     hour = second / (60 * 60);
14974     second = second % (60 * 60);
14975     minute = second / 60;
14976     second = second % 60;
14977
14978     if (day > 0)
14979       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14980               sign, day, hour, minute, second);
14981     else if (hour > 0)
14982       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14983     else
14984       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14985
14986     return buf;
14987 }
14988
14989
14990 /*
14991  * This is necessary because some C libraries aren't ANSI C compliant yet.
14992  */
14993 char *
14994 StrStr(string, match)
14995      char *string, *match;
14996 {
14997     int i, length;
14998
14999     length = strlen(match);
15000
15001     for (i = strlen(string) - length; i >= 0; i--, string++)
15002       if (!strncmp(match, string, length))
15003         return string;
15004
15005     return NULL;
15006 }
15007
15008 char *
15009 StrCaseStr(string, match)
15010      char *string, *match;
15011 {
15012     int i, j, length;
15013
15014     length = strlen(match);
15015
15016     for (i = strlen(string) - length; i >= 0; i--, string++) {
15017         for (j = 0; j < length; j++) {
15018             if (ToLower(match[j]) != ToLower(string[j]))
15019               break;
15020         }
15021         if (j == length) return string;
15022     }
15023
15024     return NULL;
15025 }
15026
15027 #ifndef _amigados
15028 int
15029 StrCaseCmp(s1, s2)
15030      char *s1, *s2;
15031 {
15032     char c1, c2;
15033
15034     for (;;) {
15035         c1 = ToLower(*s1++);
15036         c2 = ToLower(*s2++);
15037         if (c1 > c2) return 1;
15038         if (c1 < c2) return -1;
15039         if (c1 == NULLCHAR) return 0;
15040     }
15041 }
15042
15043
15044 int
15045 ToLower(c)
15046      int c;
15047 {
15048     return isupper(c) ? tolower(c) : c;
15049 }
15050
15051
15052 int
15053 ToUpper(c)
15054      int c;
15055 {
15056     return islower(c) ? toupper(c) : c;
15057 }
15058 #endif /* !_amigados    */
15059
15060 char *
15061 StrSave(s)
15062      char *s;
15063 {
15064   char *ret;
15065
15066   if ((ret = (char *) malloc(strlen(s) + 1)))
15067     {
15068       safeStrCpy(ret, s, strlen(s)+1);
15069     }
15070   return ret;
15071 }
15072
15073 char *
15074 StrSavePtr(s, savePtr)
15075      char *s, **savePtr;
15076 {
15077     if (*savePtr) {
15078         free(*savePtr);
15079     }
15080     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15081       safeStrCpy(*savePtr, s, strlen(s)+1);
15082     }
15083     return(*savePtr);
15084 }
15085
15086 char *
15087 PGNDate()
15088 {
15089     time_t clock;
15090     struct tm *tm;
15091     char buf[MSG_SIZ];
15092
15093     clock = time((time_t *)NULL);
15094     tm = localtime(&clock);
15095     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15096             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15097     return StrSave(buf);
15098 }
15099
15100
15101 char *
15102 PositionToFEN(move, overrideCastling)
15103      int move;
15104      char *overrideCastling;
15105 {
15106     int i, j, fromX, fromY, toX, toY;
15107     int whiteToPlay;
15108     char buf[128];
15109     char *p, *q;
15110     int emptycount;
15111     ChessSquare piece;
15112
15113     whiteToPlay = (gameMode == EditPosition) ?
15114       !blackPlaysFirst : (move % 2 == 0);
15115     p = buf;
15116
15117     /* Piece placement data */
15118     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15119         emptycount = 0;
15120         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15121             if (boards[move][i][j] == EmptySquare) {
15122                 emptycount++;
15123             } else { ChessSquare piece = boards[move][i][j];
15124                 if (emptycount > 0) {
15125                     if(emptycount<10) /* [HGM] can be >= 10 */
15126                         *p++ = '0' + emptycount;
15127                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15128                     emptycount = 0;
15129                 }
15130                 if(PieceToChar(piece) == '+') {
15131                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15132                     *p++ = '+';
15133                     piece = (ChessSquare)(DEMOTED piece);
15134                 }
15135                 *p++ = PieceToChar(piece);
15136                 if(p[-1] == '~') {
15137                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15138                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15139                     *p++ = '~';
15140                 }
15141             }
15142         }
15143         if (emptycount > 0) {
15144             if(emptycount<10) /* [HGM] can be >= 10 */
15145                 *p++ = '0' + emptycount;
15146             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15147             emptycount = 0;
15148         }
15149         *p++ = '/';
15150     }
15151     *(p - 1) = ' ';
15152
15153     /* [HGM] print Crazyhouse or Shogi holdings */
15154     if( gameInfo.holdingsWidth ) {
15155         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15156         q = p;
15157         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15158             piece = boards[move][i][BOARD_WIDTH-1];
15159             if( piece != EmptySquare )
15160               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15161                   *p++ = PieceToChar(piece);
15162         }
15163         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15164             piece = boards[move][BOARD_HEIGHT-i-1][0];
15165             if( piece != EmptySquare )
15166               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15167                   *p++ = PieceToChar(piece);
15168         }
15169
15170         if( q == p ) *p++ = '-';
15171         *p++ = ']';
15172         *p++ = ' ';
15173     }
15174
15175     /* Active color */
15176     *p++ = whiteToPlay ? 'w' : 'b';
15177     *p++ = ' ';
15178
15179   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15180     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15181   } else {
15182   if(nrCastlingRights) {
15183      q = p;
15184      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15185        /* [HGM] write directly from rights */
15186            if(boards[move][CASTLING][2] != NoRights &&
15187               boards[move][CASTLING][0] != NoRights   )
15188                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15189            if(boards[move][CASTLING][2] != NoRights &&
15190               boards[move][CASTLING][1] != NoRights   )
15191                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15192            if(boards[move][CASTLING][5] != NoRights &&
15193               boards[move][CASTLING][3] != NoRights   )
15194                 *p++ = boards[move][CASTLING][3] + AAA;
15195            if(boards[move][CASTLING][5] != NoRights &&
15196               boards[move][CASTLING][4] != NoRights   )
15197                 *p++ = boards[move][CASTLING][4] + AAA;
15198      } else {
15199
15200         /* [HGM] write true castling rights */
15201         if( nrCastlingRights == 6 ) {
15202             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15203                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15204             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15205                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15206             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15207                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15208             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15209                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15210         }
15211      }
15212      if (q == p) *p++ = '-'; /* No castling rights */
15213      *p++ = ' ';
15214   }
15215
15216   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15217      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15218     /* En passant target square */
15219     if (move > backwardMostMove) {
15220         fromX = moveList[move - 1][0] - AAA;
15221         fromY = moveList[move - 1][1] - ONE;
15222         toX = moveList[move - 1][2] - AAA;
15223         toY = moveList[move - 1][3] - ONE;
15224         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15225             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15226             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15227             fromX == toX) {
15228             /* 2-square pawn move just happened */
15229             *p++ = toX + AAA;
15230             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15231         } else {
15232             *p++ = '-';
15233         }
15234     } else if(move == backwardMostMove) {
15235         // [HGM] perhaps we should always do it like this, and forget the above?
15236         if((signed char)boards[move][EP_STATUS] >= 0) {
15237             *p++ = boards[move][EP_STATUS] + AAA;
15238             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15239         } else {
15240             *p++ = '-';
15241         }
15242     } else {
15243         *p++ = '-';
15244     }
15245     *p++ = ' ';
15246   }
15247   }
15248
15249     /* [HGM] find reversible plies */
15250     {   int i = 0, j=move;
15251
15252         if (appData.debugMode) { int k;
15253             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15254             for(k=backwardMostMove; k<=forwardMostMove; k++)
15255                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15256
15257         }
15258
15259         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15260         if( j == backwardMostMove ) i += initialRulePlies;
15261         sprintf(p, "%d ", i);
15262         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15263     }
15264     /* Fullmove number */
15265     sprintf(p, "%d", (move / 2) + 1);
15266
15267     return StrSave(buf);
15268 }
15269
15270 Boolean
15271 ParseFEN(board, blackPlaysFirst, fen)
15272     Board board;
15273      int *blackPlaysFirst;
15274      char *fen;
15275 {
15276     int i, j;
15277     char *p, c;
15278     int emptycount;
15279     ChessSquare piece;
15280
15281     p = fen;
15282
15283     /* [HGM] by default clear Crazyhouse holdings, if present */
15284     if(gameInfo.holdingsWidth) {
15285        for(i=0; i<BOARD_HEIGHT; i++) {
15286            board[i][0]             = EmptySquare; /* black holdings */
15287            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15288            board[i][1]             = (ChessSquare) 0; /* black counts */
15289            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15290        }
15291     }
15292
15293     /* Piece placement data */
15294     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15295         j = 0;
15296         for (;;) {
15297             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15298                 if (*p == '/') p++;
15299                 emptycount = gameInfo.boardWidth - j;
15300                 while (emptycount--)
15301                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15302                 break;
15303 #if(BOARD_FILES >= 10)
15304             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15305                 p++; emptycount=10;
15306                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15307                 while (emptycount--)
15308                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15309 #endif
15310             } else if (isdigit(*p)) {
15311                 emptycount = *p++ - '0';
15312                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15313                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15314                 while (emptycount--)
15315                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15316             } else if (*p == '+' || isalpha(*p)) {
15317                 if (j >= gameInfo.boardWidth) return FALSE;
15318                 if(*p=='+') {
15319                     piece = CharToPiece(*++p);
15320                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15321                     piece = (ChessSquare) (PROMOTED piece ); p++;
15322                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15323                 } else piece = CharToPiece(*p++);
15324
15325                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15326                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15327                     piece = (ChessSquare) (PROMOTED piece);
15328                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15329                     p++;
15330                 }
15331                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15332             } else {
15333                 return FALSE;
15334             }
15335         }
15336     }
15337     while (*p == '/' || *p == ' ') p++;
15338
15339     /* [HGM] look for Crazyhouse holdings here */
15340     while(*p==' ') p++;
15341     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15342         if(*p == '[') p++;
15343         if(*p == '-' ) p++; /* empty holdings */ else {
15344             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15345             /* if we would allow FEN reading to set board size, we would   */
15346             /* have to add holdings and shift the board read so far here   */
15347             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15348                 p++;
15349                 if((int) piece >= (int) BlackPawn ) {
15350                     i = (int)piece - (int)BlackPawn;
15351                     i = PieceToNumber((ChessSquare)i);
15352                     if( i >= gameInfo.holdingsSize ) return FALSE;
15353                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15354                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15355                 } else {
15356                     i = (int)piece - (int)WhitePawn;
15357                     i = PieceToNumber((ChessSquare)i);
15358                     if( i >= gameInfo.holdingsSize ) return FALSE;
15359                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15360                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15361                 }
15362             }
15363         }
15364         if(*p == ']') p++;
15365     }
15366
15367     while(*p == ' ') p++;
15368
15369     /* Active color */
15370     c = *p++;
15371     if(appData.colorNickNames) {
15372       if( c == appData.colorNickNames[0] ) c = 'w'; else
15373       if( c == appData.colorNickNames[1] ) c = 'b';
15374     }
15375     switch (c) {
15376       case 'w':
15377         *blackPlaysFirst = FALSE;
15378         break;
15379       case 'b':
15380         *blackPlaysFirst = TRUE;
15381         break;
15382       default:
15383         return FALSE;
15384     }
15385
15386     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15387     /* return the extra info in global variiables             */
15388
15389     /* set defaults in case FEN is incomplete */
15390     board[EP_STATUS] = EP_UNKNOWN;
15391     for(i=0; i<nrCastlingRights; i++ ) {
15392         board[CASTLING][i] =
15393             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15394     }   /* assume possible unless obviously impossible */
15395     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15396     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15397     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15398                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15399     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15400     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15401     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15402                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15403     FENrulePlies = 0;
15404
15405     while(*p==' ') p++;
15406     if(nrCastlingRights) {
15407       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15408           /* castling indicator present, so default becomes no castlings */
15409           for(i=0; i<nrCastlingRights; i++ ) {
15410                  board[CASTLING][i] = NoRights;
15411           }
15412       }
15413       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15414              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15415              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15416              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15417         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15418
15419         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15420             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15421             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15422         }
15423         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15424             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15425         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15426                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15427         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15428                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15429         switch(c) {
15430           case'K':
15431               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15432               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15433               board[CASTLING][2] = whiteKingFile;
15434               break;
15435           case'Q':
15436               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15437               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15438               board[CASTLING][2] = whiteKingFile;
15439               break;
15440           case'k':
15441               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15442               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15443               board[CASTLING][5] = blackKingFile;
15444               break;
15445           case'q':
15446               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15447               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15448               board[CASTLING][5] = blackKingFile;
15449           case '-':
15450               break;
15451           default: /* FRC castlings */
15452               if(c >= 'a') { /* black rights */
15453                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15454                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15455                   if(i == BOARD_RGHT) break;
15456                   board[CASTLING][5] = i;
15457                   c -= AAA;
15458                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15459                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15460                   if(c > i)
15461                       board[CASTLING][3] = c;
15462                   else
15463                       board[CASTLING][4] = c;
15464               } else { /* white rights */
15465                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15466                     if(board[0][i] == WhiteKing) break;
15467                   if(i == BOARD_RGHT) break;
15468                   board[CASTLING][2] = i;
15469                   c -= AAA - 'a' + 'A';
15470                   if(board[0][c] >= WhiteKing) break;
15471                   if(c > i)
15472                       board[CASTLING][0] = c;
15473                   else
15474                       board[CASTLING][1] = c;
15475               }
15476         }
15477       }
15478       for(i=0; i<nrCastlingRights; i++)
15479         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15480     if (appData.debugMode) {
15481         fprintf(debugFP, "FEN castling rights:");
15482         for(i=0; i<nrCastlingRights; i++)
15483         fprintf(debugFP, " %d", board[CASTLING][i]);
15484         fprintf(debugFP, "\n");
15485     }
15486
15487       while(*p==' ') p++;
15488     }
15489
15490     /* read e.p. field in games that know e.p. capture */
15491     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15492        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15493       if(*p=='-') {
15494         p++; board[EP_STATUS] = EP_NONE;
15495       } else {
15496          char c = *p++ - AAA;
15497
15498          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15499          if(*p >= '0' && *p <='9') p++;
15500          board[EP_STATUS] = c;
15501       }
15502     }
15503
15504
15505     if(sscanf(p, "%d", &i) == 1) {
15506         FENrulePlies = i; /* 50-move ply counter */
15507         /* (The move number is still ignored)    */
15508     }
15509
15510     return TRUE;
15511 }
15512
15513 void
15514 EditPositionPasteFEN(char *fen)
15515 {
15516   if (fen != NULL) {
15517     Board initial_position;
15518
15519     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15520       DisplayError(_("Bad FEN position in clipboard"), 0);
15521       return ;
15522     } else {
15523       int savedBlackPlaysFirst = blackPlaysFirst;
15524       EditPositionEvent();
15525       blackPlaysFirst = savedBlackPlaysFirst;
15526       CopyBoard(boards[0], initial_position);
15527       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15528       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15529       DisplayBothClocks();
15530       DrawPosition(FALSE, boards[currentMove]);
15531     }
15532   }
15533 }
15534
15535 static char cseq[12] = "\\   ";
15536
15537 Boolean set_cont_sequence(char *new_seq)
15538 {
15539     int len;
15540     Boolean ret;
15541
15542     // handle bad attempts to set the sequence
15543         if (!new_seq)
15544                 return 0; // acceptable error - no debug
15545
15546     len = strlen(new_seq);
15547     ret = (len > 0) && (len < sizeof(cseq));
15548     if (ret)
15549       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15550     else if (appData.debugMode)
15551       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15552     return ret;
15553 }
15554
15555 /*
15556     reformat a source message so words don't cross the width boundary.  internal
15557     newlines are not removed.  returns the wrapped size (no null character unless
15558     included in source message).  If dest is NULL, only calculate the size required
15559     for the dest buffer.  lp argument indicats line position upon entry, and it's
15560     passed back upon exit.
15561 */
15562 int wrap(char *dest, char *src, int count, int width, int *lp)
15563 {
15564     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15565
15566     cseq_len = strlen(cseq);
15567     old_line = line = *lp;
15568     ansi = len = clen = 0;
15569
15570     for (i=0; i < count; i++)
15571     {
15572         if (src[i] == '\033')
15573             ansi = 1;
15574
15575         // if we hit the width, back up
15576         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15577         {
15578             // store i & len in case the word is too long
15579             old_i = i, old_len = len;
15580
15581             // find the end of the last word
15582             while (i && src[i] != ' ' && src[i] != '\n')
15583             {
15584                 i--;
15585                 len--;
15586             }
15587
15588             // word too long?  restore i & len before splitting it
15589             if ((old_i-i+clen) >= width)
15590             {
15591                 i = old_i;
15592                 len = old_len;
15593             }
15594
15595             // extra space?
15596             if (i && src[i-1] == ' ')
15597                 len--;
15598
15599             if (src[i] != ' ' && src[i] != '\n')
15600             {
15601                 i--;
15602                 if (len)
15603                     len--;
15604             }
15605
15606             // now append the newline and continuation sequence
15607             if (dest)
15608                 dest[len] = '\n';
15609             len++;
15610             if (dest)
15611                 strncpy(dest+len, cseq, cseq_len);
15612             len += cseq_len;
15613             line = cseq_len;
15614             clen = cseq_len;
15615             continue;
15616         }
15617
15618         if (dest)
15619             dest[len] = src[i];
15620         len++;
15621         if (!ansi)
15622             line++;
15623         if (src[i] == '\n')
15624             line = 0;
15625         if (src[i] == 'm')
15626             ansi = 0;
15627     }
15628     if (dest && appData.debugMode)
15629     {
15630         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15631             count, width, line, len, *lp);
15632         show_bytes(debugFP, src, count);
15633         fprintf(debugFP, "\ndest: ");
15634         show_bytes(debugFP, dest, len);
15635         fprintf(debugFP, "\n");
15636     }
15637     *lp = dest ? line : old_line;
15638
15639     return len;
15640 }
15641
15642 // [HGM] vari: routines for shelving variations
15643
15644 void
15645 PushTail(int firstMove, int lastMove)
15646 {
15647         int i, j, nrMoves = lastMove - firstMove;
15648
15649         if(appData.icsActive) { // only in local mode
15650                 forwardMostMove = currentMove; // mimic old ICS behavior
15651                 return;
15652         }
15653         if(storedGames >= MAX_VARIATIONS-1) return;
15654
15655         // push current tail of game on stack
15656         savedResult[storedGames] = gameInfo.result;
15657         savedDetails[storedGames] = gameInfo.resultDetails;
15658         gameInfo.resultDetails = NULL;
15659         savedFirst[storedGames] = firstMove;
15660         savedLast [storedGames] = lastMove;
15661         savedFramePtr[storedGames] = framePtr;
15662         framePtr -= nrMoves; // reserve space for the boards
15663         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15664             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15665             for(j=0; j<MOVE_LEN; j++)
15666                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15667             for(j=0; j<2*MOVE_LEN; j++)
15668                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15669             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15670             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15671             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15672             pvInfoList[firstMove+i-1].depth = 0;
15673             commentList[framePtr+i] = commentList[firstMove+i];
15674             commentList[firstMove+i] = NULL;
15675         }
15676
15677         storedGames++;
15678         forwardMostMove = firstMove; // truncate game so we can start variation
15679         if(storedGames == 1) GreyRevert(FALSE);
15680 }
15681
15682 Boolean
15683 PopTail(Boolean annotate)
15684 {
15685         int i, j, nrMoves;
15686         char buf[8000], moveBuf[20];
15687
15688         if(appData.icsActive) return FALSE; // only in local mode
15689         if(!storedGames) return FALSE; // sanity
15690         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15691
15692         storedGames--;
15693         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15694         nrMoves = savedLast[storedGames] - currentMove;
15695         if(annotate) {
15696                 int cnt = 10;
15697                 if(!WhiteOnMove(currentMove))
15698                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15699                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15700                 for(i=currentMove; i<forwardMostMove; i++) {
15701                         if(WhiteOnMove(i))
15702                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15703                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15704                         strcat(buf, moveBuf);
15705                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15706                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15707                 }
15708                 strcat(buf, ")");
15709         }
15710         for(i=1; i<=nrMoves; i++) { // copy last variation back
15711             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15712             for(j=0; j<MOVE_LEN; j++)
15713                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15714             for(j=0; j<2*MOVE_LEN; j++)
15715                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15716             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15717             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15718             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15719             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15720             commentList[currentMove+i] = commentList[framePtr+i];
15721             commentList[framePtr+i] = NULL;
15722         }
15723         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15724         framePtr = savedFramePtr[storedGames];
15725         gameInfo.result = savedResult[storedGames];
15726         if(gameInfo.resultDetails != NULL) {
15727             free(gameInfo.resultDetails);
15728       }
15729         gameInfo.resultDetails = savedDetails[storedGames];
15730         forwardMostMove = currentMove + nrMoves;
15731         if(storedGames == 0) GreyRevert(TRUE);
15732         return TRUE;
15733 }
15734
15735 void
15736 CleanupTail()
15737 {       // remove all shelved variations
15738         int i;
15739         for(i=0; i<storedGames; i++) {
15740             if(savedDetails[i])
15741                 free(savedDetails[i]);
15742             savedDetails[i] = NULL;
15743         }
15744         for(i=framePtr; i<MAX_MOVES; i++) {
15745                 if(commentList[i]) free(commentList[i]);
15746                 commentList[i] = NULL;
15747         }
15748         framePtr = MAX_MOVES-1;
15749         storedGames = 0;
15750 }
15751
15752 void
15753 LoadVariation(int index, char *text)
15754 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15755         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15756         int level = 0, move;
15757
15758         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15759         // first find outermost bracketing variation
15760         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15761             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15762                 if(*p == '{') wait = '}'; else
15763                 if(*p == '[') wait = ']'; else
15764                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15765                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15766             }
15767             if(*p == wait) wait = NULLCHAR; // closing ]} found
15768             p++;
15769         }
15770         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15771         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15772         end[1] = NULLCHAR; // clip off comment beyond variation
15773         ToNrEvent(currentMove-1);
15774         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15775         // kludge: use ParsePV() to append variation to game
15776         move = currentMove;
15777         ParsePV(start, TRUE);
15778         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15779         ClearPremoveHighlights();
15780         CommentPopDown();
15781         ToNrEvent(currentMove+1);
15782 }
15783